RuleChain page
This commit is contained in:
		
							parent
							
								
									e85c47aebf
								
							
						
					
					
						commit
						cf1684572b
					
				
							
								
								
									
										23
									
								
								msa/js-executor/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								msa/js-executor/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1407,14 +1407,12 @@
 | 
			
		||||
        "balanced-match": {
 | 
			
		||||
          "version": "1.0.0",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "brace-expansion": {
 | 
			
		||||
          "version": "1.1.11",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "balanced-match": "^1.0.0",
 | 
			
		||||
            "concat-map": "0.0.1"
 | 
			
		||||
@ -1429,20 +1427,17 @@
 | 
			
		||||
        "code-point-at": {
 | 
			
		||||
          "version": "1.1.0",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "concat-map": {
 | 
			
		||||
          "version": "0.0.1",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "console-control-strings": {
 | 
			
		||||
          "version": "1.1.0",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "core-util-is": {
 | 
			
		||||
          "version": "1.0.2",
 | 
			
		||||
@ -1559,8 +1554,7 @@
 | 
			
		||||
        "inherits": {
 | 
			
		||||
          "version": "2.0.3",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "ini": {
 | 
			
		||||
          "version": "1.3.5",
 | 
			
		||||
@ -1572,7 +1566,6 @@
 | 
			
		||||
          "version": "1.0.0",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "number-is-nan": "^1.0.0"
 | 
			
		||||
          }
 | 
			
		||||
@ -1587,7 +1580,6 @@
 | 
			
		||||
          "version": "3.0.4",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "brace-expansion": "^1.1.7"
 | 
			
		||||
          }
 | 
			
		||||
@ -1699,8 +1691,7 @@
 | 
			
		||||
        "number-is-nan": {
 | 
			
		||||
          "version": "1.0.1",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "object-assign": {
 | 
			
		||||
          "version": "4.1.1",
 | 
			
		||||
@ -1712,7 +1703,6 @@
 | 
			
		||||
          "version": "1.4.0",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "wrappy": "1"
 | 
			
		||||
          }
 | 
			
		||||
@ -1834,7 +1824,6 @@
 | 
			
		||||
          "version": "1.0.2",
 | 
			
		||||
          "bundled": true,
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "optional": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "code-point-at": "^1.0.0",
 | 
			
		||||
            "is-fullwidth-code-point": "^1.0.0",
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,10 @@
 | 
			
		||||
            "styles": [
 | 
			
		||||
              "src/styles.scss",
 | 
			
		||||
              "node_modules/jquery.terminal/css/jquery.terminal.min.css",
 | 
			
		||||
              "node_modules/tooltipster/dist/css/tooltipster.bundle.min.css",
 | 
			
		||||
              "node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css",
 | 
			
		||||
              "src/app/shared/components/json-form/react/json-form.scss",
 | 
			
		||||
              "src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss",
 | 
			
		||||
              "node_modules/rc-select/assets/index.css"
 | 
			
		||||
            ],
 | 
			
		||||
            "stylePreprocessorOptions": {
 | 
			
		||||
@ -54,6 +57,7 @@
 | 
			
		||||
              "node_modules/flot/src/plugins/jquery.flot.stack.js",
 | 
			
		||||
              "node_modules/flot.curvedlines/curvedLines.js",
 | 
			
		||||
              "node_modules/tinycolor2/dist/tinycolor-min.js",
 | 
			
		||||
              "node_modules/tooltipster/dist/js/tooltipster.bundle.min.js",
 | 
			
		||||
              "node_modules/split.js/dist/split.js",
 | 
			
		||||
              "node_modules/js-beautify/js/lib/beautify.js",
 | 
			
		||||
              "node_modules/js-beautify/js/lib/beautify-css.js",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3480
									
								
								ui-ngx/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3480
									
								
								ui-ngx/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -12,52 +12,52 @@
 | 
			
		||||
  },
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@angular/animations": "~8.2.11",
 | 
			
		||||
    "@angular/animations": "~8.2.14",
 | 
			
		||||
    "@angular/cdk": "~8.2.3",
 | 
			
		||||
    "@angular/common": "~8.2.11",
 | 
			
		||||
    "@angular/compiler": "~8.2.11",
 | 
			
		||||
    "@angular/core": "~8.2.11",
 | 
			
		||||
    "@angular/flex-layout": "^8.0.0-beta.26",
 | 
			
		||||
    "@angular/forms": "~8.2.11",
 | 
			
		||||
    "@angular/common": "~8.2.14",
 | 
			
		||||
    "@angular/compiler": "~8.2.14",
 | 
			
		||||
    "@angular/core": "~8.2.14",
 | 
			
		||||
    "@angular/flex-layout": "^8.0.0-beta.27",
 | 
			
		||||
    "@angular/forms": "~8.2.14",
 | 
			
		||||
    "@angular/material": "^8.2.3",
 | 
			
		||||
    "@angular/platform-browser": "~8.2.11",
 | 
			
		||||
    "@angular/platform-browser-dynamic": "~8.2.11",
 | 
			
		||||
    "@angular/router": "~8.2.11",
 | 
			
		||||
    "@auth0/angular-jwt": "^3.0.0",
 | 
			
		||||
    "@date-io/date-fns": "^1.3.11",
 | 
			
		||||
    "@angular/platform-browser": "~8.2.14",
 | 
			
		||||
    "@angular/platform-browser-dynamic": "~8.2.14",
 | 
			
		||||
    "@angular/router": "~8.2.14",
 | 
			
		||||
    "@auth0/angular-jwt": "^3.0.1",
 | 
			
		||||
    "@date-io/date-fns": "^1.3.13",
 | 
			
		||||
    "@flowjs/flow.js": "^2.13.2",
 | 
			
		||||
    "@flowjs/ngx-flow": "^0.4.3",
 | 
			
		||||
    "@mat-datetimepicker/core": "^2.0.1",
 | 
			
		||||
    "@material-ui/core": "^4.5.1",
 | 
			
		||||
    "@material-ui/core": "^4.7.2",
 | 
			
		||||
    "@material-ui/icons": "^4.5.1",
 | 
			
		||||
    "@material-ui/pickers": "^3.2.7",
 | 
			
		||||
    "@ngrx/effects": "^8.2.0",
 | 
			
		||||
    "@ngrx/store": "^8.2.0",
 | 
			
		||||
    "@ngrx/store-devtools": "^8.2.0",
 | 
			
		||||
    "@ngx-share/core": "^7.1.2",
 | 
			
		||||
    "@material-ui/pickers": "^3.2.8",
 | 
			
		||||
    "@ngrx/effects": "^8.5.2",
 | 
			
		||||
    "@ngrx/store": "^8.5.2",
 | 
			
		||||
    "@ngrx/store-devtools": "^8.5.2",
 | 
			
		||||
    "@ngx-share/core": "^7.1.4",
 | 
			
		||||
    "@ngx-translate/core": "^11.0.1",
 | 
			
		||||
    "@ngx-translate/http-loader": "^4.0.0",
 | 
			
		||||
    "ace-builds": "^1.4.5",
 | 
			
		||||
    "angular-gridster2": "^8.1.0",
 | 
			
		||||
    "ace-builds": "^1.4.7",
 | 
			
		||||
    "angular-gridster2": "^8.2.0",
 | 
			
		||||
    "angular2-hotkeys": "^2.1.5",
 | 
			
		||||
    "base64-js": "^1.3.1",
 | 
			
		||||
    "compass-sass-mixins": "^0.12.7",
 | 
			
		||||
    "core-js": "^3.1.4",
 | 
			
		||||
    "date-fns": "2.1.0",
 | 
			
		||||
    "deep-equal": "^1.0.1",
 | 
			
		||||
    "core-js": "^3.5.0",
 | 
			
		||||
    "date-fns": "2.8.1",
 | 
			
		||||
    "deep-equal": "^1.1.1",
 | 
			
		||||
    "flot": "git://github.com/thingsboard/flot.git#0.9-work",
 | 
			
		||||
    "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
 | 
			
		||||
    "font-awesome": "^4.7.0",
 | 
			
		||||
    "hammerjs": "^2.0.8",
 | 
			
		||||
    "javascript-detect-element-resize": "^0.5.3",
 | 
			
		||||
    "jquery": "^3.4.1",
 | 
			
		||||
    "jquery.terminal": "^2.8.0",
 | 
			
		||||
    "jquery.terminal": "^2.9.0",
 | 
			
		||||
    "js-beautify": "^1.10.2",
 | 
			
		||||
    "json-schema-defaults": "^0.4.0",
 | 
			
		||||
    "material-design-icons": "^3.0.1",
 | 
			
		||||
    "messageformat": "^2.3.0",
 | 
			
		||||
    "moment": "^2.24.0",
 | 
			
		||||
    "ngx-clipboard": "^12.2.0",
 | 
			
		||||
    "ngx-clipboard": "^12.3.0",
 | 
			
		||||
    "ngx-color-picker": "^8.2.0",
 | 
			
		||||
    "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master",
 | 
			
		||||
    "ngx-hm-carousel": "^1.7.2",
 | 
			
		||||
@ -65,50 +65,55 @@
 | 
			
		||||
    "objectpath": "^1.2.2",
 | 
			
		||||
    "prop-types": "^15.7.2",
 | 
			
		||||
    "rc-select": "^9.2.1",
 | 
			
		||||
    "react": "^16.10.2",
 | 
			
		||||
    "react": "^16.12.0",
 | 
			
		||||
    "react-ace": "^8.0.0",
 | 
			
		||||
    "react-dom": "^16.10.2",
 | 
			
		||||
    "react-dropzone": "^10.1.10",
 | 
			
		||||
    "react-dom": "^16.12.0",
 | 
			
		||||
    "react-dropzone": "^10.2.1",
 | 
			
		||||
    "reactcss": "^1.2.3",
 | 
			
		||||
    "rxjs": "~6.5.2",
 | 
			
		||||
    "rxjs": "~6.5.3",
 | 
			
		||||
    "schema-inspector": "^1.6.8",
 | 
			
		||||
    "screenfull": "^4.2.1",
 | 
			
		||||
    "screenfull": "^5.0.0",
 | 
			
		||||
    "split.js": "^1.5.11",
 | 
			
		||||
    "tinycolor2": "^1.4.1",
 | 
			
		||||
    "tooltipster": "^4.2.7",
 | 
			
		||||
    "tslib": "^1.10.0",
 | 
			
		||||
    "tv4": "^1.3.0",
 | 
			
		||||
    "typeface-roboto": "^0.0.75",
 | 
			
		||||
    "zone.js": "~0.9.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@angular-builders/custom-webpack": "^8.2.0",
 | 
			
		||||
    "@angular-devkit/build-angular": "^0.802.0",
 | 
			
		||||
    "@angular/cli": "~8.2.2",
 | 
			
		||||
    "@angular/compiler-cli": "~8.2.11",
 | 
			
		||||
    "@angular/language-service": "~8.2.11",
 | 
			
		||||
    "@angular-builders/custom-webpack": "^8.4.1",
 | 
			
		||||
    "@angular-devkit/build-angular": "^0.803.20",
 | 
			
		||||
    "@angular/cli": "~8.3.20",
 | 
			
		||||
    "@angular/compiler-cli": "~8.2.14",
 | 
			
		||||
    "@angular/language-service": "~8.2.14",
 | 
			
		||||
    "@types/flot": "0.0.31",
 | 
			
		||||
    "@types/jasmine": "~3.4.0",
 | 
			
		||||
    "@types/jasminewd2": "~2.0.6",
 | 
			
		||||
    "@types/jasmine": "~3.5.0",
 | 
			
		||||
    "@types/jasminewd2": "~2.0.8",
 | 
			
		||||
    "@types/jquery": "^3.3.31",
 | 
			
		||||
    "@types/js-beautify": "^1.8.1",
 | 
			
		||||
    "@types/node": "~10.14.15",
 | 
			
		||||
    "@types/react": "^16.9.9",
 | 
			
		||||
    "@types/react-dom": "^16.9.2",
 | 
			
		||||
    "@types/node": "~12.12.17",
 | 
			
		||||
    "@types/react": "^16.9.16",
 | 
			
		||||
    "@types/react-dom": "^16.9.4",
 | 
			
		||||
    "@types/tinycolor2": "^1.4.2",
 | 
			
		||||
    "codelyzer": "~5.1.0",
 | 
			
		||||
    "compression-webpack-plugin": "^3.0.0",
 | 
			
		||||
    "directory-tree": "^2.2.3",
 | 
			
		||||
    "jasmine-core": "~3.4.0",
 | 
			
		||||
    "@types/tooltipster": "0.0.29",
 | 
			
		||||
    "codelyzer": "~5.2.0",
 | 
			
		||||
    "compression-webpack-plugin": "^3.0.1",
 | 
			
		||||
    "directory-tree": "^2.2.4",
 | 
			
		||||
    "jasmine-core": "~3.5.0",
 | 
			
		||||
    "jasmine-spec-reporter": "~4.2.1",
 | 
			
		||||
    "karma": "~4.2.0",
 | 
			
		||||
    "karma-chrome-launcher": "~3.0.0",
 | 
			
		||||
    "karma-coverage-istanbul-reporter": "~2.1.0",
 | 
			
		||||
    "karma": "~4.4.1",
 | 
			
		||||
    "karma-chrome-launcher": "~3.1.0",
 | 
			
		||||
    "karma-coverage-istanbul-reporter": "~2.1.1",
 | 
			
		||||
    "karma-jasmine": "~2.0.1",
 | 
			
		||||
    "karma-jasmine-html-reporter": "^1.4.2",
 | 
			
		||||
    "ngrx-store-freeze": "^0.2.4",
 | 
			
		||||
    "protractor": "~5.4.2",
 | 
			
		||||
    "ts-node": "~8.3.0",
 | 
			
		||||
    "tslint": "~5.18.0",
 | 
			
		||||
    "ts-node": "~8.5.4",
 | 
			
		||||
    "tslint": "~5.20.1",
 | 
			
		||||
    "typescript": "~3.5.3"
 | 
			
		||||
  },
 | 
			
		||||
  "resolutions": {
 | 
			
		||||
    "serialize-javascript": "^2.1.1"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ import { alarmFields } from '@shared/models/alarm.models';
 | 
			
		||||
import { materialColors } from '@app/shared/models/material.models';
 | 
			
		||||
import { WidgetInfo } from '@home/models/widget-component.models';
 | 
			
		||||
import jsonSchemaDefaults from 'json-schema-defaults';
 | 
			
		||||
import * as materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
 | 
			
		||||
import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
 | 
			
		||||
import { Observable, of, ReplaySubject } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
const varsRegex = /\$\{([^}]*)\}/g;
 | 
			
		||||
 | 
			
		||||
@ -30,9 +30,9 @@ import { entityTypeTranslations } from '@shared/models/entity-type.models';
 | 
			
		||||
import { UtilsService } from '@core/services/utils.service';
 | 
			
		||||
import { deepClone, isUndefined } from '@core/utils';
 | 
			
		||||
 | 
			
		||||
import * as customSampleJs from '!raw-loader!./custom-sample-js.raw';
 | 
			
		||||
import * as customSampleCss from '!raw-loader!./custom-sample-css.raw';
 | 
			
		||||
import * as customSampleHtml from '!raw-loader!./custom-sample-html.raw';
 | 
			
		||||
import customSampleJs from '!raw-loader!./custom-sample-js.raw';
 | 
			
		||||
import customSampleCss from '!raw-loader!./custom-sample-css.raw';
 | 
			
		||||
import customSampleHtml from '!raw-loader!./custom-sample-html.raw';
 | 
			
		||||
 | 
			
		||||
export interface WidgetActionCallbacks {
 | 
			
		||||
  fetchDashboardStates: (query: string) => Array<string>;
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { PageComponent } from '@shared/components/page.component';
 | 
			
		||||
import { Input, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Inject, Input, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models';
 | 
			
		||||
@ -38,8 +38,8 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
 | 
			
		||||
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
 | 
			
		||||
  constructor(public raf: RafService,
 | 
			
		||||
              protected store: Store<AppState>) {
 | 
			
		||||
  constructor(@Inject(RafService) public raf: RafService,
 | 
			
		||||
              @Inject(Store) protected store: Store<AppState>) {
 | 
			
		||||
    super(store);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,24 +35,48 @@
 | 
			
		||||
  <mat-sidenav-content>
 | 
			
		||||
    <div fxLayout="column" role="main" style="height: 100%;">
 | 
			
		||||
      <mat-toolbar fxLayout="row" color="primary" class="mat-elevation-z1 tb-primary-toolbar">
 | 
			
		||||
        <button [fxShow]="!forceFullscreen" mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()">
 | 
			
		||||
        <button [fxShow]="!forceFullscreen" mat-button mat-icon-button id="main"
 | 
			
		||||
                [ngClass]="{'tb-invisible': displaySearchMode()}"
 | 
			
		||||
                fxHide.gt-sm (click)="sidenav.toggle()">
 | 
			
		||||
          <mat-icon class="material-icons">menu</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button [fxShow]="forceFullscreen" mat-button mat-icon-button (click)="goBack()">
 | 
			
		||||
        <button [fxShow]="forceFullscreen" mat-button mat-icon-button
 | 
			
		||||
                [ngClass]="{'tb-invisible': displaySearchMode()}"
 | 
			
		||||
                (click)="goBack()">
 | 
			
		||||
          <mat-icon class="material-icons">arrow_back</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools">
 | 
			
		||||
        <button mat-button mat-icon-button
 | 
			
		||||
                [ngClass]="{'tb-invisible': !displaySearchMode()}"
 | 
			
		||||
                (click)="closeSearch()">
 | 
			
		||||
          <mat-icon class="material-icons">arrow_back</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div [fxShow]="!displaySearchMode()"
 | 
			
		||||
             fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools">
 | 
			
		||||
        </div>
 | 
			
		||||
        <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()">
 | 
			
		||||
        <div [fxShow]="displaySearchMode()" fxFlex fxLayout="row" class="tb-dark">
 | 
			
		||||
          <mat-form-field fxFlex floatLabel="always">
 | 
			
		||||
            <mat-label></mat-label>
 | 
			
		||||
            <input #searchInput matInput
 | 
			
		||||
                   [(ngModel)]="searchText"
 | 
			
		||||
                   placeholder="{{ 'common.enter-search' | translate }}"/>
 | 
			
		||||
          </mat-form-field>
 | 
			
		||||
        </div>
 | 
			
		||||
        <button [fxShow]="searchEnabled"
 | 
			
		||||
                mat-button mat-icon-button
 | 
			
		||||
                (click)="openSearch()">
 | 
			
		||||
          <mat-icon class="material-icons">search</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button *ngIf="fullscreenEnabled" [fxShow]="!displaySearchMode()"
 | 
			
		||||
                mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()">
 | 
			
		||||
          <mat-icon class="material-icons">{{ isFullscreen() ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <tb-user-menu [displayUserInfo]="true"></tb-user-menu>
 | 
			
		||||
        <tb-user-menu [displayUserInfo]="!displaySearchMode()"></tb-user-menu>
 | 
			
		||||
      </mat-toolbar>
 | 
			
		||||
      <mat-progress-bar color="warn" style="z-index: 10; margin-bottom: -4px; width: 100%;" mode="indeterminate"
 | 
			
		||||
                        *ngIf="isLoading$ | async">
 | 
			
		||||
      </mat-progress-bar>
 | 
			
		||||
      <div fxFlex fxLayout="column" tb-toast class="tb-main-content">
 | 
			
		||||
        <router-outlet (activate)="activeComponent = $event;"></router-outlet>
 | 
			
		||||
        <router-outlet (activate)="activeComponentChanged($event)"></router-outlet>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </mat-sidenav-content>
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,11 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  .tb-invisible {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mat-sidenav-container {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,10 @@
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { fromEvent, Observable } from 'rxjs';
 | 
			
		||||
import { select, Store } from '@ngrx/store';
 | 
			
		||||
import { map, mergeMap, take } from 'rxjs/operators';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, map, mergeMap, take, tap } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
 | 
			
		||||
import { User } from '@shared/models/user.model';
 | 
			
		||||
@ -34,19 +34,21 @@ import * as screenfull from 'screenfull';
 | 
			
		||||
import { MatSidenav } from '@angular/material';
 | 
			
		||||
import { AuthState } from '@core/auth/auth.models';
 | 
			
		||||
import { WINDOW } from '@core/services/window.service';
 | 
			
		||||
import { ISearchableComponent, instanceOfSearchableComponent } from '@home/models/searchable-component.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-home',
 | 
			
		||||
  templateUrl: './home.component.html',
 | 
			
		||||
  styleUrls: ['./home.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class HomeComponent extends PageComponent implements OnInit {
 | 
			
		||||
export class HomeComponent extends PageComponent implements AfterViewInit, OnInit {
 | 
			
		||||
 | 
			
		||||
  authState: AuthState = getCurrentAuthState(this.store);
 | 
			
		||||
 | 
			
		||||
  forceFullscreen = this.authState.forceFullscreen;
 | 
			
		||||
 | 
			
		||||
  activeComponent: any;
 | 
			
		||||
  searchableComponent: ISearchableComponent;
 | 
			
		||||
 | 
			
		||||
  sidenavMode = 'side';
 | 
			
		||||
  sidenavOpened = true;
 | 
			
		||||
@ -56,6 +58,8 @@ export class HomeComponent extends PageComponent implements OnInit {
 | 
			
		||||
  @ViewChild('sidenav', {static: false})
 | 
			
		||||
  sidenav: MatSidenav;
 | 
			
		||||
 | 
			
		||||
  @ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  fullscreenEnabled = screenfull.enabled;
 | 
			
		||||
 | 
			
		||||
@ -63,6 +67,10 @@ export class HomeComponent extends PageComponent implements OnInit {
 | 
			
		||||
  userDetails$: Observable<User>;
 | 
			
		||||
  userDetailsString: Observable<string>;
 | 
			
		||||
 | 
			
		||||
  searchEnabled = false;
 | 
			
		||||
  showSearch = false;
 | 
			
		||||
  searchText = '';
 | 
			
		||||
 | 
			
		||||
  constructor(protected store: Store<AppState>,
 | 
			
		||||
              @Inject(WINDOW) private window: Window,
 | 
			
		||||
              private authService: AuthService,
 | 
			
		||||
@ -98,6 +106,18 @@ export class HomeComponent extends PageComponent implements OnInit {
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    fromEvent(this.searchInputField.nativeElement, 'keyup')
 | 
			
		||||
      .pipe(
 | 
			
		||||
        debounceTime(150),
 | 
			
		||||
        distinctUntilChanged(),
 | 
			
		||||
        tap(() => {
 | 
			
		||||
          this.searchTextUpdated();
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  sidenavClicked() {
 | 
			
		||||
    if (this.sidenavMode === 'over') {
 | 
			
		||||
      this.sidenav.toggle();
 | 
			
		||||
@ -120,4 +140,47 @@ export class HomeComponent extends PageComponent implements OnInit {
 | 
			
		||||
  goBack() {
 | 
			
		||||
    this.window.history.back();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  activeComponentChanged(activeComponent: any) {
 | 
			
		||||
    this.showSearch = false;
 | 
			
		||||
    this.searchText = '';
 | 
			
		||||
    this.activeComponent = activeComponent;
 | 
			
		||||
    if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) {
 | 
			
		||||
      this.searchEnabled = true;
 | 
			
		||||
      this.searchableComponent = this.activeComponent;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.searchEnabled = false;
 | 
			
		||||
      this.searchableComponent = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  displaySearchMode(): boolean {
 | 
			
		||||
    return this.searchEnabled && this.showSearch;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openSearch() {
 | 
			
		||||
    if (this.searchEnabled) {
 | 
			
		||||
      this.showSearch = true;
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        this.searchInputField.nativeElement.focus();
 | 
			
		||||
        this.searchInputField.nativeElement.setSelectionRange(0, 0);
 | 
			
		||||
      }, 10);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeSearch() {
 | 
			
		||||
    if (this.searchEnabled) {
 | 
			
		||||
      this.showSearch = false;
 | 
			
		||||
      if (this.searchText.length) {
 | 
			
		||||
        this.searchText = '';
 | 
			
		||||
        this.searchTextUpdated();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private searchTextUpdated() {
 | 
			
		||||
    if (this.searchableComponent) {
 | 
			
		||||
      this.searchableComponent.onSearchTextUpdated(this.searchText);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
///
 | 
			
		||||
/// 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.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
export interface ISearchableComponent {
 | 
			
		||||
  onSearchTextUpdated(searchText: string);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function instanceOfSearchableComponent(object: any): object is ISearchableComponent {
 | 
			
		||||
  return 'onSearchTextUpdated' in object;
 | 
			
		||||
}
 | 
			
		||||
@ -30,7 +30,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
      </section>
 | 
			
		||||
      <mat-drawer-container style="width: 100%;">
 | 
			
		||||
        <mat-drawer class="tb-rulechain-library"
 | 
			
		||||
        <mat-drawer class="tb-rulechain-library mat-elevation-z4"
 | 
			
		||||
                    disableClose="true"
 | 
			
		||||
                    mode="side"
 | 
			
		||||
                    [opened]="isLibraryOpen"
 | 
			
		||||
@ -45,13 +45,13 @@
 | 
			
		||||
              </button>
 | 
			
		||||
              <mat-form-field fxFlex floatLabel="always">
 | 
			
		||||
                <mat-label></mat-label>
 | 
			
		||||
                <input matInput
 | 
			
		||||
                       [(ngModel)]="ruleNodeSearch"
 | 
			
		||||
                <input #ruleNodeSearchInput matInput
 | 
			
		||||
                       [(ngModel)]="ruleNodeTypeSearch"
 | 
			
		||||
                       placeholder="{{'rulenode.search' | translate}}"/>
 | 
			
		||||
              </mat-form-field>
 | 
			
		||||
              <button mat-button mat-icon-button class="tb-small"
 | 
			
		||||
                      [fxShow]="ruleNodeSearch !== ''"
 | 
			
		||||
                      (click)="ruleNodeSearch = ''"
 | 
			
		||||
                      [fxShow]="ruleNodeTypeSearch !== ''"
 | 
			
		||||
                      (click)="ruleNodeTypeSearch = ''; updateRuleChainLibrary()"
 | 
			
		||||
                      matTooltip="{{'action.clear-search' | translate}}"
 | 
			
		||||
                      matTooltipPosition="above">
 | 
			
		||||
                <mat-icon>close</mat-icon>
 | 
			
		||||
@ -64,17 +64,43 @@
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </mat-toolbar>
 | 
			
		||||
          <div class="tb-rulechain-library-panel-group">
 | 
			
		||||
            <mat-expansion-panel #ruleNodeTypeExpansionPanels
 | 
			
		||||
                                 class="mat-elevation-z2"
 | 
			
		||||
                                 [expanded]="true" *ngFor="let ruleNodeType of ruleNodeTypesLibraryArray">
 | 
			
		||||
              <mat-expansion-panel-header expandedHeight="48px"
 | 
			
		||||
                                          (mouseenter)="typeHeaderMouseEnter($event, ruleNodeType)"
 | 
			
		||||
                                          (mouseleave)="destroyTooltips()">
 | 
			
		||||
                <mat-panel-title>
 | 
			
		||||
                  <mat-icon style="margin-right: 8px;">{{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).icon }}</mat-icon>
 | 
			
		||||
                  <div class="tb-panel-title" translate>{{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).name }}</div>
 | 
			
		||||
                </mat-panel-title>
 | 
			
		||||
              </mat-expansion-panel-header>
 | 
			
		||||
              <fc-canvas id="tb-rulchain-{{ruleNodeType}}"
 | 
			
		||||
                         [model]="ruleNodeTypesModel[ruleNodeType].model"
 | 
			
		||||
                         [selectedObjects]="ruleNodeTypesModel[ruleNodeType].selectedObjects"
 | 
			
		||||
                         [automaticResize]="false"
 | 
			
		||||
                         [userCallbacks]="nodeLibCallbacks"
 | 
			
		||||
                         [nodeWidth]="170"
 | 
			
		||||
                         [nodeHeight]="50"
 | 
			
		||||
                         [dropTargetId]="'tb-rulchain-canvas'">
 | 
			
		||||
              </fc-canvas>
 | 
			
		||||
            </mat-expansion-panel>
 | 
			
		||||
          </div>
 | 
			
		||||
        </mat-drawer>
 | 
			
		||||
        <mat-drawer-content>
 | 
			
		||||
          <div class="tb-absolute-fill tb-rulechain-graph">
 | 
			
		||||
            <fc-canvas #fcCanvas
 | 
			
		||||
            <fc-canvas #ruleChainCanvas
 | 
			
		||||
                       id="tb-rulchain-canvas"
 | 
			
		||||
                       [model]="ruleChainModel"
 | 
			
		||||
                       (modelChanged)="onModelChanged()"
 | 
			
		||||
                       [selectedObjects]="selectedObjects"
 | 
			
		||||
                       [edgeStyle]="flowchartConstants.curvedStyle"
 | 
			
		||||
                       [automaticResize]="true"
 | 
			
		||||
                       [dragAnimation]="flowchartConstants.dragAnimationRepaint">
 | 
			
		||||
                       [nodeWidth]="170"
 | 
			
		||||
                       [nodeHeight]="50"
 | 
			
		||||
                       [dragAnimation]="flowchartConstants.dragAnimationRepaint"
 | 
			
		||||
                       [userCallbacks]="editCallbacks">
 | 
			
		||||
            </fc-canvas>
 | 
			
		||||
          </div>
 | 
			
		||||
        </mat-drawer-content>
 | 
			
		||||
 | 
			
		||||
@ -73,6 +73,32 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      .tb-rulechain-library-panel-group {
 | 
			
		||||
        overflow-x: hidden;
 | 
			
		||||
        overflow-y: auto;
 | 
			
		||||
        .mat-expansion-panel {
 | 
			
		||||
          border-radius: 0px;
 | 
			
		||||
          &:last-child {
 | 
			
		||||
            margin-bottom: 5px;
 | 
			
		||||
          }
 | 
			
		||||
          .mat-expansion-panel-header {
 | 
			
		||||
            background: #e6e6e6;
 | 
			
		||||
          }
 | 
			
		||||
          &.mat-expanded {
 | 
			
		||||
            .mat-expansion-panel-header {
 | 
			
		||||
              border-bottom: 1px solid;
 | 
			
		||||
              border-color: #909090;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        .tb-panel-title {
 | 
			
		||||
          min-width: 140px;
 | 
			
		||||
          user-select: none;
 | 
			
		||||
        }
 | 
			
		||||
        .fc-canvas {
 | 
			
		||||
          background: #f9f9f9;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .tb-rulechain-graph {
 | 
			
		||||
      z-index: 0;
 | 
			
		||||
@ -99,6 +125,11 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      .tb-rulechain-library-panel-group {
 | 
			
		||||
        .mat-expansion-panel-body {
 | 
			
		||||
          padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .fc-canvas {
 | 
			
		||||
 | 
			
		||||
@ -14,14 +14,14 @@
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
 | 
			
		||||
import { PageComponent } from '@shared/components/page.component';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { FormBuilder } from '@angular/forms';
 | 
			
		||||
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { MatDialog } from '@angular/material';
 | 
			
		||||
import { MatDialog, MatExpansionPanel } from '@angular/material';
 | 
			
		||||
import { DialogService } from '@core/services/dialog.service';
 | 
			
		||||
import { AuthService } from '@core/auth/auth.service';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
@ -31,26 +31,46 @@ import {
 | 
			
		||||
  RuleChain,
 | 
			
		||||
  ruleChainNodeComponent
 | 
			
		||||
} from '@shared/models/rule-chain.models';
 | 
			
		||||
import { FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart';
 | 
			
		||||
import { RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models';
 | 
			
		||||
import { FlowchartConstants, UserCallbacks, NgxFlowchartComponent } from 'ngx-flowchart/dist/ngx-flowchart';
 | 
			
		||||
import {
 | 
			
		||||
  RuleNodeComponentDescriptor,
 | 
			
		||||
  RuleNodeType,
 | 
			
		||||
  ruleNodeTypeDescriptors,
 | 
			
		||||
  ruleNodeTypesLibrary
 | 
			
		||||
} from '@shared/models/rule-node.models';
 | 
			
		||||
import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models';
 | 
			
		||||
import { RuleChainService } from '@core/http/rule-chain.service';
 | 
			
		||||
import { fromEvent, of } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
 | 
			
		||||
import Timeout = NodeJS.Timeout;
 | 
			
		||||
import { ISearchableComponent } from '../../models/searchable-component.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-rulechain-page',
 | 
			
		||||
  templateUrl: './rulechain-page.component.html',
 | 
			
		||||
  styleUrls: ['./rulechain-page.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag {
 | 
			
		||||
export class RuleChainPageComponent extends PageComponent
 | 
			
		||||
  implements AfterViewInit, OnInit, HasDirtyFlag, ISearchableComponent {
 | 
			
		||||
 | 
			
		||||
  get isDirty(): boolean {
 | 
			
		||||
    return this.isDirtyValue || this.isImport;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @ViewChild('ruleNodeSearchInput', {static: false}) ruleNodeSearchInputField: ElementRef;
 | 
			
		||||
 | 
			
		||||
  @ViewChild('ruleChainCanvas', {static: true}) ruleChainCanvas: NgxFlowchartComponent;
 | 
			
		||||
 | 
			
		||||
  @ViewChildren('ruleNodeTypeExpansionPanels',
 | 
			
		||||
    {read: MatExpansionPanel}) expansionPanels: QueryList<MatExpansionPanel>;
 | 
			
		||||
 | 
			
		||||
  ruleNodeTypeDescriptorsMap = ruleNodeTypeDescriptors;
 | 
			
		||||
  ruleNodeTypesLibraryArray = ruleNodeTypesLibrary;
 | 
			
		||||
 | 
			
		||||
  isImport: boolean;
 | 
			
		||||
  isDirtyValue: boolean;
 | 
			
		||||
 | 
			
		||||
  errorTooltips = {};
 | 
			
		||||
  errorTooltips: {[nodeId: string]: JQueryTooltipster.ITooltipsterInstance} = {};
 | 
			
		||||
  isFullscreen = false;
 | 
			
		||||
 | 
			
		||||
  editingRuleNode = null;
 | 
			
		||||
@ -62,6 +82,7 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
  isLibraryOpen = true;
 | 
			
		||||
 | 
			
		||||
  ruleNodeSearch = '';
 | 
			
		||||
  ruleNodeTypeSearch = '';
 | 
			
		||||
 | 
			
		||||
  ruleChain: RuleChain;
 | 
			
		||||
  ruleChainMetaData: ResolvedRuleChainMetaData;
 | 
			
		||||
@ -71,16 +92,58 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
    edges: []
 | 
			
		||||
  };
 | 
			
		||||
  selectedObjects = [];
 | 
			
		||||
 | 
			
		||||
  editCallbacks: UserCallbacks = {
 | 
			
		||||
    edgeDoubleClick: (event, edge) => {
 | 
			
		||||
      console.log('TODO');
 | 
			
		||||
    },
 | 
			
		||||
    edgeEdit: (event, edge) => {
 | 
			
		||||
      console.log('TODO');
 | 
			
		||||
    },
 | 
			
		||||
    nodeCallbacks: {
 | 
			
		||||
      doubleClick: (event, node) => {
 | 
			
		||||
        console.log('TODO');
 | 
			
		||||
      },
 | 
			
		||||
      nodeEdit: (event, node) => {
 | 
			
		||||
        console.log('TODO');
 | 
			
		||||
      },
 | 
			
		||||
      mouseEnter: this.displayNodeDescriptionTooltip.bind(this),
 | 
			
		||||
      mouseLeave: this.destroyTooltips.bind(this),
 | 
			
		||||
      mouseDown: this.destroyTooltips.bind(this)
 | 
			
		||||
    },
 | 
			
		||||
    isValidEdge: (source, destination) => {
 | 
			
		||||
      return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType;
 | 
			
		||||
    },
 | 
			
		||||
    createEdge: (event, edge) => {
 | 
			
		||||
      console.log('TODO');
 | 
			
		||||
      return of(edge);
 | 
			
		||||
    },
 | 
			
		||||
    dropNode: (event, node) => {
 | 
			
		||||
      console.log('TODO dropNode');
 | 
			
		||||
      console.log(node);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  nextNodeID: number;
 | 
			
		||||
  nextConnectorID: number;
 | 
			
		||||
  inputConnectorId: number;
 | 
			
		||||
 | 
			
		||||
  ruleNodeTypesModel: {[type: string]: {model: FcRuleNodeTypeModel, selectedObjects: any[]}} = {};
 | 
			
		||||
 | 
			
		||||
  nodeLibCallbacks: UserCallbacks = {
 | 
			
		||||
    nodeCallbacks: {
 | 
			
		||||
      mouseEnter: this.displayLibNodeDescriptionTooltip.bind(this),
 | 
			
		||||
      mouseLeave: this.destroyTooltips.bind(this),
 | 
			
		||||
      mouseDown: this.destroyTooltips.bind(this)
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  ruleNodeComponents: Array<RuleNodeComponentDescriptor>;
 | 
			
		||||
 | 
			
		||||
  flowchartConstants = FlowchartConstants;
 | 
			
		||||
 | 
			
		||||
  private tooltipTimeout: Timeout;
 | 
			
		||||
 | 
			
		||||
  constructor(protected store: Store<AppState>,
 | 
			
		||||
              private route: ActivatedRoute,
 | 
			
		||||
              private router: Router,
 | 
			
		||||
@ -97,6 +160,23 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    fromEvent(this.ruleNodeSearchInputField.nativeElement, 'keyup')
 | 
			
		||||
      .pipe(
 | 
			
		||||
        debounceTime(150),
 | 
			
		||||
        distinctUntilChanged(),
 | 
			
		||||
        tap(() => {
 | 
			
		||||
          this.updateRuleChainLibrary();
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSearchTextUpdated(searchText: string) {
 | 
			
		||||
    this.ruleNodeSearch = searchText;
 | 
			
		||||
    this.updateRuleNodesHighlight();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private init() {
 | 
			
		||||
    this.ruleChain = this.route.snapshot.data.ruleChain;
 | 
			
		||||
    if (this.route.snapshot.data.import && !this.ruleChain) {
 | 
			
		||||
@ -106,8 +186,8 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
    this.isImport = this.route.snapshot.data.import;
 | 
			
		||||
    this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData;
 | 
			
		||||
    this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents;
 | 
			
		||||
    for (const type of Object.keys(RuleNodeType)) {
 | 
			
		||||
      const desc = ruleNodeTypeDescriptors.get(RuleNodeType[type]);
 | 
			
		||||
    for (const type of ruleNodeTypesLibrary) {
 | 
			
		||||
      const desc = ruleNodeTypeDescriptors.get(type);
 | 
			
		||||
      if (!desc.special) {
 | 
			
		||||
        this.ruleNodeTypesModel[type] = {
 | 
			
		||||
          model: {
 | 
			
		||||
@ -118,10 +198,17 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.loadRuleChainLibrary(this.ruleNodeComponents);
 | 
			
		||||
    this.updateRuleChainLibrary();
 | 
			
		||||
    this.createRuleChainModel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateRuleChainLibrary() {
 | 
			
		||||
    const search = this.ruleNodeTypeSearch.toUpperCase();
 | 
			
		||||
    const res = this.ruleNodeComponents.filter(
 | 
			
		||||
      (ruleNodeComponent) => ruleNodeComponent.name.toUpperCase().includes(search));
 | 
			
		||||
    this.loadRuleChainLibrary(res);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private loadRuleChainLibrary(ruleNodeComponents: Array<RuleNodeComponentDescriptor>) {
 | 
			
		||||
    for (const componentType of Object.keys(this.ruleNodeTypesModel)) {
 | 
			
		||||
      this.ruleNodeTypesModel[componentType].model.nodes.length = 0;
 | 
			
		||||
@ -167,6 +254,21 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
      }
 | 
			
		||||
      model.nodes.push(node);
 | 
			
		||||
    });
 | 
			
		||||
    if (this.expansionPanels) {
 | 
			
		||||
      for (let i = 0; i < ruleNodeTypesLibrary.length; i++) {
 | 
			
		||||
        const panel = this.expansionPanels.find((item, index) => {
 | 
			
		||||
          return index === i;
 | 
			
		||||
        });
 | 
			
		||||
        if (panel) {
 | 
			
		||||
          const type = ruleNodeTypesLibrary[i];
 | 
			
		||||
          if (!this.ruleNodeTypesModel[type].model.nodes.length) {
 | 
			
		||||
            panel.close();
 | 
			
		||||
          } else {
 | 
			
		||||
            panel.open();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private createRuleChainModel() {
 | 
			
		||||
@ -350,5 +452,115 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has
 | 
			
		||||
    this.isDirtyValue = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  typeHeaderMouseEnter(event: MouseEvent, ruleNodeType: RuleNodeType) {
 | 
			
		||||
    const type = ruleNodeTypeDescriptors.get(ruleNodeType);
 | 
			
		||||
    this.displayTooltip(event,
 | 
			
		||||
      '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
 | 
			
		||||
      '<div id="tb-node-content" layout="column">' +
 | 
			
		||||
      '<div class="tb-node-title">' + this.translate.instant(type.name) + '</div>' +
 | 
			
		||||
      '<div class="tb-node-details">' + this.translate.instant(type.details) + '</div>' +
 | 
			
		||||
      '</div>' +
 | 
			
		||||
      '</div>'
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) {
 | 
			
		||||
    this.displayTooltip(event,
 | 
			
		||||
      '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
 | 
			
		||||
      '<div id="tb-node-content" layout="column">' +
 | 
			
		||||
      '<div class="tb-node-title">' + node.component.name + '</div>' +
 | 
			
		||||
      '<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' +
 | 
			
		||||
      '<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' +
 | 
			
		||||
      '</div>' +
 | 
			
		||||
      '</div>'
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  displayNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNode) {
 | 
			
		||||
    if (!this.errorTooltips[node.id]) {
 | 
			
		||||
      let name: string;
 | 
			
		||||
      let desc: string;
 | 
			
		||||
      let details: string;
 | 
			
		||||
      if (node.component.type === RuleNodeType.INPUT) {
 | 
			
		||||
        name = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).name);
 | 
			
		||||
        desc = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).details);
 | 
			
		||||
      } else {
 | 
			
		||||
        name = node.name;
 | 
			
		||||
        desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name;
 | 
			
		||||
        if (node.additionalInfo) {
 | 
			
		||||
          details = node.additionalInfo.description;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      let tooltipContent = '<div class="tb-rule-node-tooltip">' +
 | 
			
		||||
        '<div id="tb-node-content" layout="column">' +
 | 
			
		||||
        '<div class="tb-node-title">' + name + '</div>' +
 | 
			
		||||
        '<div class="tb-node-description">' + desc + '</div>';
 | 
			
		||||
      if (details) {
 | 
			
		||||
        tooltipContent += '<div class="tb-node-details">' + details + '</div>';
 | 
			
		||||
      }
 | 
			
		||||
      tooltipContent += '</div>' +
 | 
			
		||||
        '</div>';
 | 
			
		||||
      this.displayTooltip(event, tooltipContent);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  destroyTooltips() {
 | 
			
		||||
    if (this.tooltipTimeout) {
 | 
			
		||||
      clearTimeout(this.tooltipTimeout);
 | 
			
		||||
      this.tooltipTimeout = null;
 | 
			
		||||
    }
 | 
			
		||||
    const instances = $.tooltipster.instances();
 | 
			
		||||
    instances.forEach((instance) => {
 | 
			
		||||
      if (!instance.isErrorTooltip) {
 | 
			
		||||
        instance.destroy();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateRuleNodesHighlight() {
 | 
			
		||||
    for (const ruleNode of this.ruleChainModel.nodes) {
 | 
			
		||||
      ruleNode.highlighted = false;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.ruleNodeSearch) {
 | 
			
		||||
      const search = this.ruleNodeSearch.toUpperCase();
 | 
			
		||||
      const res = this.ruleChainModel.nodes.filter(node => node.name.toUpperCase().includes(search));
 | 
			
		||||
      if (res) {
 | 
			
		||||
        for (const ruleNode of res) {
 | 
			
		||||
          ruleNode.highlighted = true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.ruleChainCanvas.modelService.detectChanges();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private displayTooltip(event: MouseEvent, content: string) {
 | 
			
		||||
    this.destroyTooltips();
 | 
			
		||||
    this.tooltipTimeout = setTimeout(() => {
 | 
			
		||||
      const element = $(event.target);
 | 
			
		||||
      element.tooltipster(
 | 
			
		||||
        {
 | 
			
		||||
          theme: 'tooltipster-shadow',
 | 
			
		||||
          delay: 100,
 | 
			
		||||
          trigger: 'custom',
 | 
			
		||||
          triggerOpen: {
 | 
			
		||||
            click: false,
 | 
			
		||||
            tap: false
 | 
			
		||||
          },
 | 
			
		||||
          triggerClose: {
 | 
			
		||||
            click: true,
 | 
			
		||||
            tap: true,
 | 
			
		||||
            scroll: true
 | 
			
		||||
          },
 | 
			
		||||
          side: 'right',
 | 
			
		||||
          trackOrigin: true
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
      const contentElement = $(content);
 | 
			
		||||
      const tooltip = element.tooltipster('instance');
 | 
			
		||||
      tooltip.content(contentElement);
 | 
			
		||||
      tooltip.open();
 | 
			
		||||
    }, 500);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ export interface FcRuleNode extends FcRuleNodeType {
 | 
			
		||||
  debugMode?: boolean;
 | 
			
		||||
  targetRuleChainId?: string;
 | 
			
		||||
  error?: string;
 | 
			
		||||
  highlighted?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FcRuleEdge extends FcEdge {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
.tooltipster-base {
 | 
			
		||||
  .tb-rule-node-tooltip,
 | 
			
		||||
  .tb-rule-node-help {
 | 
			
		||||
    color: #333;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-rule-node-tooltip {
 | 
			
		||||
    max-width: 300px;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
 | 
			
		||||
    &.tb-lib-tooltip {
 | 
			
		||||
      width: 300px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-rule-node-help {
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-rule-node-error-tooltip {
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    color: #ea0d0d;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-rule-node-tooltip,
 | 
			
		||||
  .tb-rule-node-error-tooltip,
 | 
			
		||||
  .tb-rule-node-help {
 | 
			
		||||
    #tb-node-content {
 | 
			
		||||
      .tb-node-title {
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .tb-node-description {
 | 
			
		||||
        font-style: italic;
 | 
			
		||||
        color: #555;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .tb-node-details {
 | 
			
		||||
        padding-top: 10px;
 | 
			
		||||
        padding-bottom: 10px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      code {
 | 
			
		||||
        padding: 0 3px 2px 3px;
 | 
			
		||||
        margin: 1px;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        color: #ad1625;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        background-color: #f7f7f9;
 | 
			
		||||
        border: 1px solid #e1e1e8;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -22,8 +22,8 @@
 | 
			
		||||
   (mouseleave)="userNodeCallbacks.mouseLeave($event, node)">
 | 
			
		||||
  <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
 | 
			
		||||
  <div class="tb-rule-node {{node.nodeClass}}" [ngClass]="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }">
 | 
			
		||||
    <mat-icon *ngIf="!node.iconUrl" fxFlex="15">{{node.icon}}</mat-icon>
 | 
			
		||||
    <img *ngIf="node.iconUrl" fxFlex="15" src="{{node.iconUrl}}"/>
 | 
			
		||||
    <mat-icon *ngIf="!iconUrl" fxFlex="15">{{node.icon}}</mat-icon>
 | 
			
		||||
    <img *ngIf="iconUrl" fxFlex="15" [src]="iconUrl"/>
 | 
			
		||||
    <div fxLayout="column" fxFlex="85" fxLayoutAlign="center">
 | 
			
		||||
      <span class="tb-node-type">{{ node.component.name }}</span>
 | 
			
		||||
      <span class="tb-node-title" *ngIf="node.name">{{ node.name }}</span>
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,8 @@
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@ -22,10 +23,19 @@ import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart';
 | 
			
		||||
  templateUrl: './rulenode.component.html',
 | 
			
		||||
  styleUrls: ['./rulenode.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class RuleNodeComponent extends FcNodeComponent {
 | 
			
		||||
export class RuleNodeComponent extends FcNodeComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
  iconUrl: SafeResourceUrl;
 | 
			
		||||
 | 
			
		||||
  constructor(private sanitizer: DomSanitizer) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    super.ngOnInit();
 | 
			
		||||
    if (this.node.iconUrl) {
 | 
			
		||||
      this.iconUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.node.iconUrl);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,15 @@ export enum RuleNodeType {
 | 
			
		||||
  INPUT = 'INPUT'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ruleNodeTypesLibrary = [
 | 
			
		||||
  RuleNodeType.FILTER,
 | 
			
		||||
  RuleNodeType.ENRICHMENT,
 | 
			
		||||
  RuleNodeType.TRANSFORMATION,
 | 
			
		||||
  RuleNodeType.ACTION,
 | 
			
		||||
  RuleNodeType.EXTERNAL,
 | 
			
		||||
  RuleNodeType.RULE_CHAIN,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export interface RuleNodeTypeDescriptor {
 | 
			
		||||
  value: RuleNodeType;
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  "extends": "../tsconfig.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "outDir": "../out-tsc/app",
 | 
			
		||||
    "types": ["node", "jquery", "flot", "tinycolor2", "js-beautify", "react", "react-dom"]
 | 
			
		||||
    "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom"]
 | 
			
		||||
  },
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "test.ts",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user