Browse Source

Custom actions implementation

pull/2150/head
Igor Kulikov 7 years ago
parent
commit
1c10ede1c5
  1. 2
      ui-ngx/package.json
  2. 3
      ui-ngx/src/app/core/api/widget-api.models.ts
  3. 2
      ui-ngx/src/app/core/services/dialog.service.ts
  4. 1
      ui-ngx/src/app/core/services/dynamic-component-factory.service.ts
  5. 14
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  6. 8
      ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw
  7. 45
      ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw
  8. 40
      ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw
  9. 85
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts
  10. 48
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts
  11. 85
      ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts
  12. 13
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  13. 7
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  14. 4
      ui-ngx/src/app/shared/components/fullscreen.directive.ts
  15. 1
      ui-ngx/src/styles.scss

2
ui-ngx/package.json

@ -3,7 +3,7 @@
"version": "3.0.0", "version": "3.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --open", "start": "ng serve --host 0.0.0.0 --open",
"build": "ng build", "build": "ng build",
"build:prod": "ng build --prod --vendor-chunk", "build:prod": "ng build --prod --vendor-chunk",
"test": "ng test", "test": "ng test",

3
ui-ngx/src/app/core/api/widget-api.models.ts

@ -36,6 +36,9 @@ import { DatasourceService } from '@core/api/datasource.service';
import { RafService } from '@core/services/raf.service'; import { RafService } from '@core/services/raf.service';
import { EntityAliases } from '@shared/models/alias.models'; import { EntityAliases } from '@shared/models/alias.models';
import { EntityInfo } from '@app/shared/models/entity.models'; import { EntityInfo } from '@app/shared/models/entity.models';
import { Type } from '@angular/core';
import { AssetService } from '@core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
export interface TimewindowFunctions { export interface TimewindowFunctions {
onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;

2
ui-ngx/src/app/core/services/dialog.service.ts

@ -30,6 +30,7 @@ import {
MaterialIconsDialogComponent, MaterialIconsDialogComponent,
MaterialIconsDialogData MaterialIconsDialogData
} from '@shared/components/dialog/material-icons-dialog.component'; } from '@shared/components/dialog/material-icons-dialog.component';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
@Injectable( @Injectable(
{ {
@ -41,6 +42,7 @@ export class DialogService {
constructor( constructor(
private translate: TranslateService, private translate: TranslateService,
private authService: AuthService, private authService: AuthService,
private dynamicComponentFactoryService: DynamicComponentFactoryService,
public dialog: MatDialog public dialog: MatDialog
) { ) {
} }

1
ui-ngx/src/app/core/services/dynamic-component-factory.service.ts

@ -31,7 +31,6 @@ import { CommonModule } from '@angular/common';
export abstract class DynamicComponentModule implements OnDestroy { export abstract class DynamicComponentModule implements OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
console.log('Module destroyed!');
} }
} }

14
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -56,6 +56,8 @@ import { ManageWidgetActionsComponent } from './widget/action/manage-widget-acti
import { WidgetActionDialogComponent } from './widget/action/widget-action-dialog.component'; import { WidgetActionDialogComponent } from './widget/action/widget-action-dialog.component';
import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom-action-pretty-resources-tabs.component'; import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom-action-pretty-resources-tabs.component';
import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component'; import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component';
import { CustomDialogService } from './widget/dialog/custom-dialog.service';
import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component';
@NgModule({ @NgModule({
entryComponents: [ entryComponents: [
@ -72,7 +74,8 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
EntityAliasDialogComponent, EntityAliasDialogComponent,
DataKeyConfigDialogComponent, DataKeyConfigDialogComponent,
LegendConfigPanelComponent, LegendConfigPanelComponent,
WidgetActionDialogComponent WidgetActionDialogComponent,
CustomDialogContainerComponent
], ],
declarations: declarations:
[ [
@ -113,7 +116,8 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
ManageWidgetActionsComponent, ManageWidgetActionsComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent CustomActionPrettyEditorComponent,
CustomDialogContainerComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -149,10 +153,12 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
ManageWidgetActionsComponent, ManageWidgetActionsComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent CustomActionPrettyEditorComponent,
CustomDialogContainerComponent
], ],
providers: [ providers: [
WidgetComponentService WidgetComponentService,
CustomDialogService
] ]
}) })
export class HomeComponentsModule { } export class HomeComponentsModule { }

8
ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw

@ -1,3 +1,11 @@
/*================================================================================*/
/*======================= New TB 3.0 / Angular 8 Example =======================*/
/*================================================================================*/
/*
.edit-entity-form mat-form-field {
padding-right: 10px;
}
*/
/*=======================================================================*/ /*=======================================================================*/
/*========== There are two examples: for edit and add entity ==========*/ /*========== There are two examples: for edit and add entity ==========*/
/*=======================================================================*/ /*=======================================================================*/

45
ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw

@ -1,4 +1,49 @@
<!--=======================================================================--> <!--=======================================================================-->
<!--=================== New TB 3.0 / Angular 8 Example =================-->
<!--=======================================================================-->
<!--<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"-->
<!-- class="edit-entity-form"-->
<!-- (ngSubmit)="save()" style="width: 600px;">-->
<!-- <mat-toolbar fxLayout="row" color="primary">-->
<!-- <h2>Edit {{entityType.toLowerCase()}} {{entityName}}</h2>-->
<!-- <span fxFlex></span>-->
<!-- <button mat-button mat-icon-button-->
<!-- (click)="cancel()"-->
<!-- type="button">-->
<!-- <mat-icon class="material-icons">close</mat-icon>-->
<!-- </button>-->
<!-- </mat-toolbar>-->
<!-- <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">-->
<!-- </mat-progress-bar>-->
<!-- <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>-->
<!-- <div mat-dialog-content>-->
<!-- <div class="mat-padding" fxLayout="column">-->
<!-- <mat-form-field class="mat-block">-->
<!-- <mat-label>Entity name</mat-label>-->
<!-- <input matInput formControlName="entityName" required>-->
<!-- <mat-error *ngIf="editEntityFormGroup.get('entityName').hasError('required')">-->
<!-- Entity name required-->
<!-- </mat-error>-->
<!-- </mat-form-field>-->
<!-- </div> -->
<!-- </div>-->
<!-- <div mat-dialog-actions fxLayout="row">-->
<!-- <span fxFlex></span>-->
<!-- <button mat-button mat-raised-button color="primary"-->
<!-- type="submit"-->
<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
<!-- {{ 'action.save' | translate }}-->
<!-- </button>-->
<!-- <button mat-button color="primary"-->
<!-- style="margin-right: 20px;"-->
<!-- type="button"-->
<!-- [disabled]="(isLoading$ | async)"-->
<!-- (click)="cancel()" cdkFocusInitial>-->
<!-- {{ 'action.cancel' | translate }}-->
<!-- </button>-->
<!-- </div>-->
<!--</form>-->
<!--=======================================================================-->
<!--===== There are two example templates: for edit and add entity =====--> <!--===== There are two example templates: for edit and add entity =====-->
<!--=======================================================================--> <!--=======================================================================-->
<!--======================== Edit entity example ========================--> <!--======================== Edit entity example ========================-->

40
ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw

@ -1,3 +1,43 @@
/*================================================================================*/
/*======================= New TB 3.0 / Angular 8 Example =======================*/
/*================================================================================*/
//
//let $injector = widgetContext.$scope.$injector;
//let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
//
//deviceService.getDevice(entityId.id).subscribe(function(device) {
// console.log(device);
//});
//
//
//let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
//
//customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe(
// function(res) {
// console.log(res);
// }
//);
//
//function EditEntityDialogController(instance) {
// let vm = instance;
// vm.entityId = entityId;
// vm.entityName = entityName;
// vm.entityType = entityId.entityType;
//
// vm.editEntityFormGroup = vm.fb.group({
// entityName: [vm.entityName, [vm.Validators.required]]
// });
//
// vm.cancel = function() {
// vm.dialogRef.close(null);
// };
//
// vm.save = function() {
// const newVal = vm.editEntityFormGroup.value;
// vm.dialogRef.close(newVal);
// };
//}
//
/*=======================================================================*/ /*=======================================================================*/
/*===== There are three examples: for delete, edit and add entity =====*/ /*===== There are three examples: for delete, edit and add entity =====*/
/*=======================================================================*/ /*=======================================================================*/

85
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts

@ -0,0 +1,85 @@
///
/// 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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
Inject,
ComponentRef,
ElementRef,
ViewContainerRef,
ComponentFactory,
Injector,
ReflectiveInjector,
Component,
OnDestroy
} from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import {
CustomDialogComponent,
CUSTOM_DIALOG_DATA,
CustomDialogData
} from '@home/components/widget/dialog/custom-dialog.component';
export interface CustomDialogContainerData {
controller: (instance: CustomDialogComponent) => void;
data?: any;
customComponentFactory: ComponentFactory<CustomDialogComponent>;
}
@Component({
selector: 'tb-custom-dialog-container-component',
template: ''
})
export class CustomDialogContainerComponent extends DialogComponent<CustomDialogContainerComponent, any> implements OnDestroy {
private customComponentRef: ComponentRef<CustomDialogComponent>;
constructor(protected store: Store<AppState>,
protected router: Router,
public viewContainerRef: ViewContainerRef,
public dialogRef: MatDialogRef<CustomDialogContainerComponent, any>,
@Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
super(store, router, dialogRef);
let customDialogData: CustomDialogData = {
controller: this.data.controller
};
if (this.data.data) {
customDialogData = {...customDialogData, ...this.data.data};
}
const injector: Injector = ReflectiveInjector.resolveAndCreate([
{
provide: CUSTOM_DIALOG_DATA,
useValue: customDialogData
},
{
provide: MatDialogRef,
useValue: dialogRef
}
]);
this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
}
ngOnDestroy(): void {
super.ngOnDestroy();
if (this.customComponentRef) {
this.customComponentRef.destroy();
}
}
}

48
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts

@ -0,0 +1,48 @@
///
/// 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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Inject, InjectionToken } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { PageComponent } from '@shared/components/page.component';
import { CustomDialogContainerComponent } from './custom-dialog-container.component';
import { FormBuilder, Validators } from '@angular/forms';
export const CUSTOM_DIALOG_DATA = new InjectionToken<any>('ConfigDialogData');
export interface CustomDialogData {
controller: (instance: CustomDialogComponent) => void;
[key: string]: any;
}
export class CustomDialogComponent extends PageComponent {
[key: string]: any;
constructor(protected store: Store<AppState>,
protected router: Router,
public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
public fb: FormBuilder,
@Inject(CUSTOM_DIALOG_DATA) public data: CustomDialogData) {
super(store);
// @ts-ignore
this.Validators = Validators;
this.data.controller(this);
}
}

85
ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts

@ -0,0 +1,85 @@
///
/// 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 { Injectable, Injector, NgModule } from '@angular/core';
import { Observable } from 'rxjs';
import { MatDialog } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { AuthService } from '@core/auth/auth.service';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { mergeMap, tap } from 'rxjs/operators';
import { CustomDialogComponent } from './custom-dialog.component';
import {
CustomDialogContainerComponent,
CustomDialogContainerData
} from '@home/components/widget/dialog/custom-dialog-container.component';
@Injectable()
export class CustomDialogService {
constructor(
private translate: TranslateService,
private authService: AuthService,
private dynamicComponentFactoryService: DynamicComponentFactoryService,
private injector: Injector,
public dialog: MatDialog
) {
}
customDialog(template: string, controller: (instance: CustomDialogComponent) => void, data?: any): Observable<any> {
return this.dynamicComponentFactoryService.createDynamicComponentFactory(
class CustomDialogComponentInstance extends CustomDialogComponent {},
template,
[SharedModule, CustomDialogModule]).pipe(
mergeMap((factory) => {
const dialogData: CustomDialogContainerData = {
controller,
customComponentFactory: factory,
data
};
return this.dialog.open<CustomDialogContainerComponent, CustomDialogContainerData, any>(
CustomDialogContainerComponent,
{
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: dialogData
}).afterClosed().pipe(
tap(() => {
this.dynamicComponentFactoryService.destroyDynamicComponentFactory(factory);
})
);
}
));
}
}
@NgModule({
entryComponents: [
],
declarations:
[
],
imports: [
CommonModule,
SharedModule
],
exports: [
]
})
class CustomDialogModule { }

13
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -28,7 +28,7 @@ import {
OnChanges, OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
SimpleChanges, SimpleChanges, Type,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
ViewEncapsulation ViewEncapsulation
@ -90,6 +90,15 @@ import { DashboardService } from '@core/http/dashboard.service';
import { DatasourceService } from '@core/api/datasource.service'; import { DatasourceService } from '@core/api/datasource.service';
import { WidgetSubscription } from '@core/api/widget-subscription'; import { WidgetSubscription } from '@core/api/widget-subscription';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { AssetService } from '@core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
const ServicesMap = new Map<string, Type<any>>();
ServicesMap.set('deviceService', DeviceService);
ServicesMap.set('assetService', AssetService);
ServicesMap.set('dialogs', DialogService);
ServicesMap.set('customDialog', CustomDialogService);
@Component({ @Component({
selector: 'tb-widget', selector: 'tb-widget',
@ -242,6 +251,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
this.widgetContext = this.dashboardWidget.widgetContext; this.widgetContext = this.dashboardWidget.widgetContext;
this.widgetContext.servicesMap = ServicesMap;
this.widgetContext.inited = false; this.widgetContext.inited = false;
this.widgetContext.hideTitlePanel = false; this.widgetContext.hideTitlePanel = false;
this.widgetContext.isEdit = this.isEdit; this.widgetContext.isEdit = this.isEdit;
@ -647,6 +657,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.dynamicWidgetComponent.errorMessages = this.errorMessages; this.dynamicWidgetComponent.errorMessages = this.errorMessages;
this.widgetContext.$scope = this.dynamicWidgetComponent; this.widgetContext.$scope = this.dynamicWidgetComponent;
this.widgetContext.$scope.$injector = this.injector;
const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container')); const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container'));

7
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -39,12 +39,16 @@ import {
WidgetActionsApi, WidgetActionsApi,
WidgetSubscriptionApi WidgetSubscriptionApi
} from '@core/api/widget-api.models'; } from '@core/api/widget-api.models';
import { ComponentFactory } from '@angular/core'; import { ComponentFactory, Type } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { RafService } from '@core/services/raf.service'; import { RafService } from '@core/services/raf.service';
import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { WidgetLayout } from '@shared/models/dashboard.models'; import { WidgetLayout } from '@shared/models/dashboard.models';
import { DeviceService } from '@core/http/device.service';
import { AssetService } from '@app/core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
export interface IWidgetAction { export interface IWidgetAction {
name: string; name: string;
@ -98,6 +102,7 @@ export interface WidgetContext {
customHeaderActions?: Array<WidgetHeaderAction>; customHeaderActions?: Array<WidgetHeaderAction>;
widgetActions?: Array<WidgetAction>; widgetActions?: Array<WidgetAction>;
servicesMap?: Map<string, Type<any>>;
} }
export interface IDynamicWidgetComponent { export interface IDynamicWidgetComponent {

4
ui-ngx/src/app/shared/components/fullscreen.directive.ts

@ -92,7 +92,7 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
this.overlayRef = this.overlay.create(config); this.overlayRef = this.overlay.create(config);
this.overlayRef.attach(new EmptyPortal()); this.overlayRef.attach(new EmptyPortal());
this.overlayRef.overlayElement.append( targetElement ); this.overlayRef.overlayElement.appendChild( targetElement );
this.fullscreenChanged.emit(true); this.fullscreenChanged.emit(true);
} }
@ -100,7 +100,7 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement; const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement;
if (this.parentElement) { if (this.parentElement) {
this.overlayRef.overlayElement.removeChild( targetElement ); this.overlayRef.overlayElement.removeChild( targetElement );
this.parentElement.append(targetElement); this.parentElement.appendChild(targetElement);
this.parentElement = null; this.parentElement = null;
} }
targetElement.classList.remove('tb-fullscreen'); targetElement.classList.remove('tb-fullscreen');

1
ui-ngx/src/styles.scss

@ -332,6 +332,7 @@ pre.tb-highlight {
.tb-fullscreen-parent { .tb-fullscreen-parent {
background: #eee; background: #eee;
z-index: 0;
} }
mat-label { mat-label {

Loading…
Cancel
Save