GPIO widgets

This commit is contained in:
Igor Kulikov 2020-02-03 17:29:01 +02:00
parent 7261c75c61
commit 2807c497f0
17 changed files with 227 additions and 66 deletions

File diff suppressed because one or more lines are too long

View File

@ -3640,6 +3640,12 @@
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
"dev": true
},
"@types/raphael": {
"version": "2.1.30",
"resolved": "https://registry.npmjs.org/@types/raphael/-/raphael-2.1.30.tgz",
"integrity": "sha1-dsvqSlVrol6xxtf6XnGsSOcvgc8=",
"dev": true
},
"@types/react": {
"version": "16.9.16",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz",
@ -6620,6 +6626,11 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
"eve-raphael": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/eve-raphael/-/eve-raphael-0.5.0.tgz",
"integrity": "sha1-F8dUt5K+7z+maE15z1pHxjxM2jA="
},
"eventemitter3": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
@ -10955,6 +10966,14 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true
},
"raphael": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/raphael/-/raphael-2.3.0.tgz",
"integrity": "sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ==",
"requires": {
"eve-raphael": "0.5.0"
}
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",

View File

@ -66,6 +66,7 @@
"ngx-translate-messageformat-compiler": "^4.5.0",
"objectpath": "^1.2.2",
"prop-types": "^15.7.2",
"raphael": "^2.3.0",
"rc-select": "^9.2.1",
"react": "^16.12.0",
"react-ace": "^8.0.0",
@ -97,6 +98,7 @@
"@types/js-beautify": "^1.8.1",
"@types/jstree": "^3.3.39",
"@types/node": "~12.12.17",
"@types/raphael": "^2.1.30",
"@types/react": "^16.9.16",
"@types/react-dom": "^16.9.4",
"@types/tinycolor2": "^1.4.2",

View File

@ -552,8 +552,9 @@ export class WidgetSubscription implements IWidgetSubscription {
}, 500);
} else {
this.executingSubjects.push(rpcSubject);
const targetSendFunction = oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand : this.ctx.deviceService.sendTwoWayRpcCommand;
targetSendFunction(this.targetDeviceId, requestBody).subscribe((responseBody) => {
(oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) :
this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody))
.subscribe((responseBody) => {
this.rpcRejection = null;
this.rpcErrorText = null;
const index = this.executingSubjects.indexOf(rpcSubject);

View File

@ -47,7 +47,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
private AUTH_HEADER_NAME = 'X-Authorization';
private internalUrlPrefixes = [
'/api/auth/token'
'/api/auth/token',
'/api/plugins/rpc'
];
private activeRequests = 0;
@ -125,8 +126,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
const ignoreErrors = config.ignoreErrors;
const resendRequest = config.resendRequest;
const errorCode = errorResponse.error ? errorResponse.error.errorCode : null;
if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) {
if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) {
if (errorResponse.error && errorResponse.error.refreshTokenPending || errorResponse.status === 401) {
if (errorResponse.error && errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) {
return this.refreshTokenAndRetry(req, next);
} else if (errorCode !== Constants.serverErrorCode.credentialsExpired) {
unhandled = true;

View File

@ -201,7 +201,7 @@ export class WidgetComponentService {
}
if (widgetControllerDescriptor) {
const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;
this.loadWidgetResources(widgetInfo, widgetNamespace, [WidgetComponentsModule]).subscribe(
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule]).subscribe(
() => {
if (widgetControllerDescriptor.settingsSchema) {
widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;

View File

@ -27,18 +27,19 @@ import {
NgZone,
OnChanges,
OnDestroy,
OnInit, ReflectiveInjector,
SimpleChanges, Type,
OnInit,
SimpleChanges,
Type,
ViewChild,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models';
import { DashboardWidget } from '@home/models/dashboard-component.models';
import {
Datasource,
defaultLegendConfig,
LegendConfig,
LegendData,
LegendDirection,
LegendPosition,
Widget,
WidgetActionDescriptor,
@ -46,8 +47,7 @@ import {
WidgetActionType,
WidgetResource,
widgetType,
WidgetTypeParameters,
defaultLegendConfig
WidgetTypeParameters
} from '@shared/models/widget.models';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
@ -101,6 +101,7 @@ ServicesMap.set('assetService', AssetService);
ServicesMap.set('dialogs', DialogService);
ServicesMap.set('customDialog', CustomDialogService);
ServicesMap.set('date', DatePipe);
ServicesMap.set('utils', UtilsService);
@Component({
selector: 'tb-widget',
@ -252,6 +253,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetContext = this.dashboardWidget.widgetContext;
this.widgetContext.changeDetector = this.cd;
this.widgetContext.ngZone = this.ngZone;
this.widgetContext.servicesMap = ServicesMap;
this.widgetContext.isEdit = this.isEdit;
this.widgetContext.isMobile = this.isMobile;
@ -533,7 +535,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private reInitImpl() {
this.onDestroy();
this.configureDynamicWidgetComponent();
if (!this.typeParameters.useCustomDatasources) {
this.createDefaultSubscription().subscribe(
() => {
@ -541,6 +542,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.onDestroy();
} else {
this.subscriptionInited = true;
this.configureDynamicWidgetComponent();
this.cd.detectChanges();
this.onInit();
}
},
@ -555,6 +558,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
);
} else {
this.subscriptionInited = true;
this.configureDynamicWidgetComponent();
this.cd.detectChanges();
this.onInit();
}
}
@ -826,6 +831,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled;
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
this.cd.detectChanges();
}
},
onRpcSuccess: (subscription) => {
@ -833,6 +839,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
this.cd.detectChanges();
}
},
onRpcFailed: (subscription) => {
@ -840,12 +847,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
this.cd.detectChanges();
}
},
onRpcErrorCleared: (subscription) => {
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.rpcErrorText = null;
this.dynamicWidgetComponent.rpcRejection = null;
this.cd.detectChanges();
}
}
};

View File

@ -198,6 +198,8 @@ export class WidgetContext {
servicesMap?: Map<string, Type<any>>;
$injector?: Injector;
ngZone?: NgZone;
}
export interface IDynamicWidgetComponent {

View File

@ -14,22 +14,11 @@
/// limitations under the License.
///
import {
Component,
OnInit,
ViewEncapsulation,
Input,
OnDestroy,
OnChanges,
SimpleChanges,
NgZone
} from '@angular/core';
import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models';
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { StateObject, StateParams } from '@core/api/widget-api.models';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription, of } from 'rxjs';
import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models';
import { DashboardState } from '@shared/models/dashboard.models';
import { IStateControllerComponent, StateControllerState } from './state-controller.models';
import { StateControllerState } from './state-controller.models';
import { StateControllerComponent } from './state-controller.component';
import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service';
import { EntityId } from '@app/shared/models/id/entity-id';
@ -37,8 +26,6 @@ import { UtilsService } from '@core/services/utils.service';
import { base64toObj, objToBase64 } from '@app/core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service';
import { EntityType } from '@shared/models/entity-type.models';
import { map } from 'rxjs/operators';
@Component({
selector: 'tb-default-state-controller',
@ -49,11 +36,12 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
constructor(protected router: Router,
protected route: ActivatedRoute,
protected ngZone: NgZone,
protected statesControllerService: StatesControllerService,
private utils: UtilsService,
private entityService: EntityService,
private dashboardUtils: DashboardUtilsService) {
super(router, route, statesControllerService);
super(router, route, ngZone, statesControllerService);
}
ngOnInit(): void {

View File

@ -14,22 +14,11 @@
/// limitations under the License.
///
import {
Component,
OnInit,
ViewEncapsulation,
Input,
OnDestroy,
OnChanges,
SimpleChanges,
NgZone
} from '@angular/core';
import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models';
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { StateObject, StateParams } from '@core/api/widget-api.models';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription, of } from 'rxjs';
import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models';
import { DashboardState } from '@shared/models/dashboard.models';
import { IStateControllerComponent, StateControllerState } from './state-controller.models';
import { Observable, of } from 'rxjs';
import { StateControllerState } from './state-controller.models';
import { StateControllerComponent } from './state-controller.component';
import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service';
import { EntityId } from '@app/shared/models/id/entity-id';
@ -51,11 +40,12 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
constructor(protected router: Router,
protected route: ActivatedRoute,
protected ngZone: NgZone,
protected statesControllerService: StatesControllerService,
private utils: UtilsService,
private entityService: EntityService,
private dashboardUtils: DashboardUtilsService) {
super(router, route, statesControllerService);
super(router, route, ngZone, statesControllerService);
}
ngOnInit(): void {

View File

@ -18,7 +18,7 @@ import { IStateControllerComponent, StateControllerState } from '@home/pages/das
import { IDashboardController } from '../dashboard-page.models';
import { DashboardState } from '@app/shared/models/dashboard.models';
import { Subscription } from 'rxjs';
import { OnDestroy, OnInit } from '@angular/core';
import { NgZone, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service';
import { EntityId } from '@app/shared/models/id/entity-id';
@ -91,6 +91,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
constructor(protected router: Router,
protected route: ActivatedRoute,
protected ngZone: NgZone,
protected statesControllerService: StatesControllerService) {
}
@ -121,12 +122,14 @@ export abstract class StateControllerComponent implements IStateControllerCompon
protected updateStateParam(newState: string) {
this.currentState = newState;
const queryParams: Params = { state: this.currentState };
this.router.navigate(
[],
{
relativeTo: this.route,
queryParams,
queryParamsHandling: 'merge',
this.ngZone.run(() => {
this.router.navigate(
[],
{
relativeTo: this.route,
queryParams,
queryParamsHandling: 'merge',
});
});
}

View File

@ -157,6 +157,7 @@ class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardAr
addButton = <Button variant='contained'
color='primary'
startIcon={<AddIcon/>}
style={{marginBottom: '8px'}}
onClick={this.onAppend}>{this.props.form.add || 'New'}</Button>;
}

View File

@ -314,7 +314,7 @@ function defaultFormDefinition(name: string, schema: any, options: DefaultsFormO
const rules = defaults[stripNullType(schema.type)];
if (rules) {
let def;
rules.forEach((rule) => {
for (const rule of rules) {
def = rule(name, schema, options);
if (def) {
@ -324,7 +324,7 @@ function defaultFormDefinition(name: string, schema: any, options: DefaultsFormO
}
return def;
}
});
}
}
}

View File

@ -0,0 +1,18 @@
<!--
Copyright © 2016-2019 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.
-->
<div id="canvas_container" [ngStyle]="{width: size + 'px', height: size + 'px'}"></div>

View File

@ -0,0 +1,124 @@
///
/// Copyright © 2016-2019 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 { AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import Raphael from 'raphael';
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
interface CircleElement extends RaphaelElement {
theGlow?: RaphaelSet;
}
@Component({
selector: 'tb-led-light',
templateUrl: './led-light.component.html',
styleUrls: []
})
export class LedLightComponent implements OnInit, AfterViewInit, OnChanges {
@Input() size: number;
@Input() colorOn: string;
@Input() colorOff: string;
@Input() offOpacity: string;
private enabledValue: boolean;
get enabled(): boolean {
return this.enabledValue;
}
@Input()
set enabled(value: boolean) {
this.enabledValue = coerceBooleanProperty(value);
}
private canvasSize: number;
private radius: number;
private glowSize: number;
private glowColor: string;
private paper: RaphaelPaper;
private circleElement: CircleElement;
constructor(private elementRef: ElementRef<HTMLElement>) {
}
ngOnInit(): void {
this.offOpacity = this.offOpacity || '0.4';
this.glowColor = tinycolor(this.colorOn).lighten().toHexString();
}
ngAfterViewInit(): void {
this.update();
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'enabled') {
this.draw();
} else if (propName === 'size') {
this.update();
}
}
}
}
private update() {
this.size = this.size || 50;
this.canvasSize = this.size;
this.radius = this.canvasSize / 4;
this.glowSize = this.radius / 5;
if (this.paper) {
this.paper.remove();
}
this.paper = Raphael($('#canvas_container', this.elementRef.nativeElement)[0], this.canvasSize, this.canvasSize);
const center = this.canvasSize / 2;
this.circleElement = this.paper.circle(center, center, this.radius);
this.draw();
}
private draw() {
if (this.enabled) {
this.circleElement.attr('fill', this.colorOn);
this.circleElement.attr('stroke', this.colorOn);
this.circleElement.attr('opacity', '1');
if (this.circleElement.theGlow) {
this.circleElement.theGlow.remove();
}
this.circleElement.theGlow = this.circleElement.glow(
{
color: this.glowColor,
width: this.radius + this.glowSize,
opacity: 0.8,
fill: true
});
} else {
if (this.circleElement.theGlow) {
this.circleElement.theGlow.remove();
}
this.circleElement.attr('fill', this.colorOff);
this.circleElement.attr('stroke', this.colorOff);
this.circleElement.attr('opacity', this.offOpacity);
}
}
}

View File

@ -121,6 +121,7 @@ import { KeyValMapComponent } from './components/kv-map.component';
import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbHotkeysDirective } from '@shared/components/hotkeys.directive';
import { NavTreeComponent } from '@shared/components/nav-tree.component';
import { LedLightComponent } from '@shared/components/led-light.component';
@NgModule({
providers: [
@ -200,6 +201,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component';
MessageTypeAutocompleteComponent,
KeyValMapComponent,
NavTreeComponent,
LedLightComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
@ -349,6 +351,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component';
MessageTypeAutocompleteComponent,
KeyValMapComponent,
NavTreeComponent,
LedLightComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,

View File

@ -2,7 +2,7 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree"]
"types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree", "raphael"]
},
"exclude": [
"test.ts",