Commit 1c10ede1c594532329f09ede08f211ff77f967ef

Authored by Igor Kulikov
1 parent c3ebc2f2

Custom actions implementation

... ... @@ -3,7 +3,7 @@
3 3 "version": "3.0.0",
4 4 "scripts": {
5 5 "ng": "ng",
6   - "start": "ng serve --open",
  6 + "start": "ng serve --host 0.0.0.0 --open",
7 7 "build": "ng build",
8 8 "build:prod": "ng build --prod --vendor-chunk",
9 9 "test": "ng test",
... ...
... ... @@ -36,6 +36,9 @@ import { DatasourceService } from '@core/api/datasource.service';
36 36 import { RafService } from '@core/services/raf.service';
37 37 import { EntityAliases } from '@shared/models/alias.models';
38 38 import { EntityInfo } from '@app/shared/models/entity.models';
  39 +import { Type } from '@angular/core';
  40 +import { AssetService } from '@core/http/asset.service';
  41 +import { DialogService } from '@core/services/dialog.service';
39 42
40 43 export interface TimewindowFunctions {
41 44 onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
... ...
... ... @@ -30,6 +30,7 @@ import {
30 30 MaterialIconsDialogComponent,
31 31 MaterialIconsDialogData
32 32 } from '@shared/components/dialog/material-icons-dialog.component';
  33 +import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
33 34
34 35 @Injectable(
35 36 {
... ... @@ -41,6 +42,7 @@ export class DialogService {
41 42 constructor(
42 43 private translate: TranslateService,
43 44 private authService: AuthService,
  45 + private dynamicComponentFactoryService: DynamicComponentFactoryService,
44 46 public dialog: MatDialog
45 47 ) {
46 48 }
... ...
... ... @@ -31,7 +31,6 @@ import { CommonModule } from '@angular/common';
31 31 export abstract class DynamicComponentModule implements OnDestroy {
32 32
33 33 ngOnDestroy(): void {
34   - console.log('Module destroyed!');
35 34 }
36 35
37 36 }
... ...
... ... @@ -56,6 +56,8 @@ import { ManageWidgetActionsComponent } from './widget/action/manage-widget-acti
56 56 import { WidgetActionDialogComponent } from './widget/action/widget-action-dialog.component';
57 57 import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom-action-pretty-resources-tabs.component';
58 58 import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component';
  59 +import { CustomDialogService } from './widget/dialog/custom-dialog.service';
  60 +import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component';
59 61
60 62 @NgModule({
61 63 entryComponents: [
... ... @@ -72,7 +74,8 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
72 74 EntityAliasDialogComponent,
73 75 DataKeyConfigDialogComponent,
74 76 LegendConfigPanelComponent,
75   - WidgetActionDialogComponent
  77 + WidgetActionDialogComponent,
  78 + CustomDialogContainerComponent
76 79 ],
77 80 declarations:
78 81 [
... ... @@ -113,7 +116,8 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
113 116 ManageWidgetActionsComponent,
114 117 WidgetActionDialogComponent,
115 118 CustomActionPrettyResourcesTabsComponent,
116   - CustomActionPrettyEditorComponent
  119 + CustomActionPrettyEditorComponent,
  120 + CustomDialogContainerComponent
117 121 ],
118 122 imports: [
119 123 CommonModule,
... ... @@ -149,10 +153,12 @@ import { CustomActionPrettyEditorComponent } from './widget/action/custom-action
149 153 ManageWidgetActionsComponent,
150 154 WidgetActionDialogComponent,
151 155 CustomActionPrettyResourcesTabsComponent,
152   - CustomActionPrettyEditorComponent
  156 + CustomActionPrettyEditorComponent,
  157 + CustomDialogContainerComponent
153 158 ],
154 159 providers: [
155   - WidgetComponentService
  160 + WidgetComponentService,
  161 + CustomDialogService
156 162 ]
157 163 })
158 164 export class HomeComponentsModule { }
... ...
  1 +/*================================================================================*/
  2 +/*======================= New TB 3.0 / Angular 8 Example =======================*/
  3 +/*================================================================================*/
  4 +/*
  5 +.edit-entity-form mat-form-field {
  6 + padding-right: 10px;
  7 +}
  8 +*/
1 9 /*=======================================================================*/
2 10 /*========== There are two examples: for edit and add entity ==========*/
3 11 /*=======================================================================*/
... ...
1 1 <!--=======================================================================-->
  2 +<!--=================== New TB 3.0 / Angular 8 Example =================-->
  3 +<!--=======================================================================-->
  4 +<!--<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"-->
  5 +<!-- class="edit-entity-form"-->
  6 +<!-- (ngSubmit)="save()" style="width: 600px;">-->
  7 +<!-- <mat-toolbar fxLayout="row" color="primary">-->
  8 +<!-- <h2>Edit {{entityType.toLowerCase()}} {{entityName}}</h2>-->
  9 +<!-- <span fxFlex></span>-->
  10 +<!-- <button mat-button mat-icon-button-->
  11 +<!-- (click)="cancel()"-->
  12 +<!-- type="button">-->
  13 +<!-- <mat-icon class="material-icons">close</mat-icon>-->
  14 +<!-- </button>-->
  15 +<!-- </mat-toolbar>-->
  16 +<!-- <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">-->
  17 +<!-- </mat-progress-bar>-->
  18 +<!-- <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>-->
  19 +<!-- <div mat-dialog-content>-->
  20 +<!-- <div class="mat-padding" fxLayout="column">-->
  21 +<!-- <mat-form-field class="mat-block">-->
  22 +<!-- <mat-label>Entity name</mat-label>-->
  23 +<!-- <input matInput formControlName="entityName" required>-->
  24 +<!-- <mat-error *ngIf="editEntityFormGroup.get('entityName').hasError('required')">-->
  25 +<!-- Entity name required-->
  26 +<!-- </mat-error>-->
  27 +<!-- </mat-form-field>-->
  28 +<!-- </div> -->
  29 +<!-- </div>-->
  30 +<!-- <div mat-dialog-actions fxLayout="row">-->
  31 +<!-- <span fxFlex></span>-->
  32 +<!-- <button mat-button mat-raised-button color="primary"-->
  33 +<!-- type="submit"-->
  34 +<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
  35 +<!-- {{ 'action.save' | translate }}-->
  36 +<!-- </button>-->
  37 +<!-- <button mat-button color="primary"-->
  38 +<!-- style="margin-right: 20px;"-->
  39 +<!-- type="button"-->
  40 +<!-- [disabled]="(isLoading$ | async)"-->
  41 +<!-- (click)="cancel()" cdkFocusInitial>-->
  42 +<!-- {{ 'action.cancel' | translate }}-->
  43 +<!-- </button>-->
  44 +<!-- </div>-->
  45 +<!--</form>-->
  46 +<!--=======================================================================-->
2 47 <!--===== There are two example templates: for edit and add entity =====-->
3 48 <!--=======================================================================-->
4 49 <!--======================== Edit entity example ========================-->
... ...
  1 +/*================================================================================*/
  2 +/*======================= New TB 3.0 / Angular 8 Example =======================*/
  3 +/*================================================================================*/
  4 +//
  5 +//let $injector = widgetContext.$scope.$injector;
  6 +//let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
  7 +//
  8 +//deviceService.getDevice(entityId.id).subscribe(function(device) {
  9 +// console.log(device);
  10 +//});
  11 +//
  12 +//
  13 +//let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
  14 +//
  15 +//customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe(
  16 +// function(res) {
  17 +// console.log(res);
  18 +// }
  19 +//);
  20 +//
  21 +//function EditEntityDialogController(instance) {
  22 +// let vm = instance;
  23 +// vm.entityId = entityId;
  24 +// vm.entityName = entityName;
  25 +// vm.entityType = entityId.entityType;
  26 +//
  27 +// vm.editEntityFormGroup = vm.fb.group({
  28 +// entityName: [vm.entityName, [vm.Validators.required]]
  29 +// });
  30 +//
  31 +// vm.cancel = function() {
  32 +// vm.dialogRef.close(null);
  33 +// };
  34 +//
  35 +// vm.save = function() {
  36 +// const newVal = vm.editEntityFormGroup.value;
  37 +// vm.dialogRef.close(newVal);
  38 +// };
  39 +//}
  40 +//
1 41 /*=======================================================================*/
2 42 /*===== There are three examples: for delete, edit and add entity =====*/
3 43 /*=======================================================================*/
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
  18 +import {
  19 + Inject,
  20 + ComponentRef,
  21 + ElementRef,
  22 + ViewContainerRef,
  23 + ComponentFactory,
  24 + Injector,
  25 + ReflectiveInjector,
  26 + Component,
  27 + OnDestroy
  28 +} from '@angular/core';
  29 +import { DialogComponent } from '@shared/components/dialog.component';
  30 +import { Store } from '@ngrx/store';
  31 +import { AppState } from '@core/core.state';
  32 +import { Router } from '@angular/router';
  33 +import {
  34 + CustomDialogComponent,
  35 + CUSTOM_DIALOG_DATA,
  36 + CustomDialogData
  37 +} from '@home/components/widget/dialog/custom-dialog.component';
  38 +
  39 +export interface CustomDialogContainerData {
  40 + controller: (instance: CustomDialogComponent) => void;
  41 + data?: any;
  42 + customComponentFactory: ComponentFactory<CustomDialogComponent>;
  43 +}
  44 +
  45 +@Component({
  46 + selector: 'tb-custom-dialog-container-component',
  47 + template: ''
  48 +})
  49 +export class CustomDialogContainerComponent extends DialogComponent<CustomDialogContainerComponent, any> implements OnDestroy {
  50 +
  51 + private customComponentRef: ComponentRef<CustomDialogComponent>;
  52 +
  53 + constructor(protected store: Store<AppState>,
  54 + protected router: Router,
  55 + public viewContainerRef: ViewContainerRef,
  56 + public dialogRef: MatDialogRef<CustomDialogContainerComponent, any>,
  57 + @Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) {
  58 + super(store, router, dialogRef);
  59 + let customDialogData: CustomDialogData = {
  60 + controller: this.data.controller
  61 + };
  62 + if (this.data.data) {
  63 + customDialogData = {...customDialogData, ...this.data.data};
  64 + }
  65 + const injector: Injector = ReflectiveInjector.resolveAndCreate([
  66 + {
  67 + provide: CUSTOM_DIALOG_DATA,
  68 + useValue: customDialogData
  69 + },
  70 + {
  71 + provide: MatDialogRef,
  72 + useValue: dialogRef
  73 + }
  74 + ]);
  75 + this.customComponentRef = this.viewContainerRef.createComponent(this.data.customComponentFactory, 0, injector);
  76 + }
  77 +
  78 + ngOnDestroy(): void {
  79 + super.ngOnDestroy();
  80 + if (this.customComponentRef) {
  81 + this.customComponentRef.destroy();
  82 + }
  83 + }
  84 +
  85 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
  18 +import { Inject, InjectionToken } from '@angular/core';
  19 +import { DialogComponent } from '@shared/components/dialog.component';
  20 +import { Store } from '@ngrx/store';
  21 +import { AppState } from '@core/core.state';
  22 +import { Router } from '@angular/router';
  23 +import { PageComponent } from '@shared/components/page.component';
  24 +import { CustomDialogContainerComponent } from './custom-dialog-container.component';
  25 +import { FormBuilder, Validators } from '@angular/forms';
  26 +
  27 +export const CUSTOM_DIALOG_DATA = new InjectionToken<any>('ConfigDialogData');
  28 +
  29 +export interface CustomDialogData {
  30 + controller: (instance: CustomDialogComponent) => void;
  31 + [key: string]: any;
  32 +}
  33 +
  34 +export class CustomDialogComponent extends PageComponent {
  35 +
  36 + [key: string]: any;
  37 +
  38 + constructor(protected store: Store<AppState>,
  39 + protected router: Router,
  40 + public dialogRef: MatDialogRef<CustomDialogContainerComponent>,
  41 + public fb: FormBuilder,
  42 + @Inject(CUSTOM_DIALOG_DATA) public data: CustomDialogData) {
  43 + super(store);
  44 + // @ts-ignore
  45 + this.Validators = Validators;
  46 + this.data.controller(this);
  47 + }
  48 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable, Injector, NgModule } from '@angular/core';
  18 +import { Observable } from 'rxjs';
  19 +import { MatDialog } from '@angular/material';
  20 +import { TranslateService } from '@ngx-translate/core';
  21 +import { AuthService } from '@core/auth/auth.service';
  22 +import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
  23 +import { CommonModule } from '@angular/common';
  24 +import { SharedModule } from '@shared/shared.module';
  25 +import { mergeMap, tap } from 'rxjs/operators';
  26 +import { CustomDialogComponent } from './custom-dialog.component';
  27 +import {
  28 + CustomDialogContainerComponent,
  29 + CustomDialogContainerData
  30 +} from '@home/components/widget/dialog/custom-dialog-container.component';
  31 +
  32 +@Injectable()
  33 +export class CustomDialogService {
  34 +
  35 + constructor(
  36 + private translate: TranslateService,
  37 + private authService: AuthService,
  38 + private dynamicComponentFactoryService: DynamicComponentFactoryService,
  39 + private injector: Injector,
  40 + public dialog: MatDialog
  41 + ) {
  42 + }
  43 +
  44 + customDialog(template: string, controller: (instance: CustomDialogComponent) => void, data?: any): Observable<any> {
  45 + return this.dynamicComponentFactoryService.createDynamicComponentFactory(
  46 + class CustomDialogComponentInstance extends CustomDialogComponent {},
  47 + template,
  48 + [SharedModule, CustomDialogModule]).pipe(
  49 + mergeMap((factory) => {
  50 + const dialogData: CustomDialogContainerData = {
  51 + controller,
  52 + customComponentFactory: factory,
  53 + data
  54 + };
  55 + return this.dialog.open<CustomDialogContainerComponent, CustomDialogContainerData, any>(
  56 + CustomDialogContainerComponent,
  57 + {
  58 + disableClose: true,
  59 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  60 + data: dialogData
  61 + }).afterClosed().pipe(
  62 + tap(() => {
  63 + this.dynamicComponentFactoryService.destroyDynamicComponentFactory(factory);
  64 + })
  65 + );
  66 + }
  67 + ));
  68 + }
  69 +
  70 +}
  71 +
  72 +@NgModule({
  73 + entryComponents: [
  74 + ],
  75 + declarations:
  76 + [
  77 + ],
  78 + imports: [
  79 + CommonModule,
  80 + SharedModule
  81 + ],
  82 + exports: [
  83 + ]
  84 +})
  85 +class CustomDialogModule { }
... ...
... ... @@ -28,7 +28,7 @@ import {
28 28 OnChanges,
29 29 OnDestroy,
30 30 OnInit,
31   - SimpleChanges,
  31 + SimpleChanges, Type,
32 32 ViewChild,
33 33 ViewContainerRef,
34 34 ViewEncapsulation
... ... @@ -90,6 +90,15 @@ import { DashboardService } from '@core/http/dashboard.service';
90 90 import { DatasourceService } from '@core/api/datasource.service';
91 91 import { WidgetSubscription } from '@core/api/widget-subscription';
92 92 import { EntityService } from '@core/http/entity.service';
  93 +import { AssetService } from '@core/http/asset.service';
  94 +import { DialogService } from '@core/services/dialog.service';
  95 +import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
  96 +
  97 +const ServicesMap = new Map<string, Type<any>>();
  98 +ServicesMap.set('deviceService', DeviceService);
  99 +ServicesMap.set('assetService', AssetService);
  100 +ServicesMap.set('dialogs', DialogService);
  101 +ServicesMap.set('customDialog', CustomDialogService);
93 102
94 103 @Component({
95 104 selector: 'tb-widget',
... ... @@ -242,6 +251,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
242 251 }
243 252
244 253 this.widgetContext = this.dashboardWidget.widgetContext;
  254 + this.widgetContext.servicesMap = ServicesMap;
245 255 this.widgetContext.inited = false;
246 256 this.widgetContext.hideTitlePanel = false;
247 257 this.widgetContext.isEdit = this.isEdit;
... ... @@ -647,6 +657,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
647 657 this.dynamicWidgetComponent.errorMessages = this.errorMessages;
648 658
649 659 this.widgetContext.$scope = this.dynamicWidgetComponent;
  660 + this.widgetContext.$scope.$injector = this.injector;
650 661
651 662 const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container'));
652 663
... ...
... ... @@ -39,12 +39,16 @@ import {
39 39 WidgetActionsApi,
40 40 WidgetSubscriptionApi
41 41 } from '@core/api/widget-api.models';
42   -import { ComponentFactory } from '@angular/core';
  42 +import { ComponentFactory, Type } from '@angular/core';
43 43 import { HttpErrorResponse } from '@angular/common/http';
44 44 import { RafService } from '@core/services/raf.service';
45 45 import { WidgetTypeId } from '@shared/models/id/widget-type-id';
46 46 import { TenantId } from '@shared/models/id/tenant-id';
47 47 import { WidgetLayout } from '@shared/models/dashboard.models';
  48 +import { DeviceService } from '@core/http/device.service';
  49 +import { AssetService } from '@app/core/http/asset.service';
  50 +import { DialogService } from '@core/services/dialog.service';
  51 +import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
48 52
49 53 export interface IWidgetAction {
50 54 name: string;
... ... @@ -98,6 +102,7 @@ export interface WidgetContext {
98 102 customHeaderActions?: Array<WidgetHeaderAction>;
99 103 widgetActions?: Array<WidgetAction>;
100 104
  105 + servicesMap?: Map<string, Type<any>>;
101 106 }
102 107
103 108 export interface IDynamicWidgetComponent {
... ...
... ... @@ -92,7 +92,7 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
92 92
93 93 this.overlayRef = this.overlay.create(config);
94 94 this.overlayRef.attach(new EmptyPortal());
95   - this.overlayRef.overlayElement.append( targetElement );
  95 + this.overlayRef.overlayElement.appendChild( targetElement );
96 96 this.fullscreenChanged.emit(true);
97 97 }
98 98
... ... @@ -100,7 +100,7 @@ export class FullscreenDirective implements OnChanges, OnDestroy {
100 100 const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement;
101 101 if (this.parentElement) {
102 102 this.overlayRef.overlayElement.removeChild( targetElement );
103   - this.parentElement.append(targetElement);
  103 + this.parentElement.appendChild(targetElement);
104 104 this.parentElement = null;
105 105 }
106 106 targetElement.classList.remove('tb-fullscreen');
... ...
... ... @@ -332,6 +332,7 @@ pre.tb-highlight {
332 332
333 333 .tb-fullscreen-parent {
334 334 background: #eee;
  335 + z-index: 0;
335 336 }
336 337
337 338 mat-label {
... ...