Commit be770518d7d509025d0bbba4dc088656f5d03a03

Authored by Artem Halushko
1 parent 5d33da4b

WIP on trip animation widget

@@ -38,7 +38,6 @@ @@ -38,7 +38,6 @@
38 "@ngx-share/core": "^7.1.4", 38 "@ngx-share/core": "^7.1.4",
39 "@ngx-translate/core": "^12.1.1", 39 "@ngx-translate/core": "^12.1.1",
40 "@ngx-translate/http-loader": "^4.0.0", 40 "@ngx-translate/http-loader": "^4.0.0",
41 - "@types/lodash": "^4.14.149",  
42 "ace-builds": "^1.4.8", 41 "ace-builds": "^1.4.8",
43 "angular-gridster2": "^9.0.1", 42 "angular-gridster2": "^9.0.1",
44 "angular2-hotkeys": "^2.1.5", 43 "angular2-hotkeys": "^2.1.5",
@@ -105,6 +104,7 @@ @@ -105,6 +104,7 @@
105 "@types/js-beautify": "^1.8.1", 104 "@types/js-beautify": "^1.8.1",
106 "@types/jstree": "^3.3.39", 105 "@types/jstree": "^3.3.39",
107 "@types/leaflet": "^1.5.9", 106 "@types/leaflet": "^1.5.9",
  107 + "@types/lodash": "^4.14.149",
108 "@types/raphael": "^2.1.30", 108 "@types/raphael": "^2.1.30",
109 "@types/react": "^16.9.20", 109 "@types/react": "^16.9.20",
110 "@types/react-dom": "^16.9.5", 110 "@types/react-dom": "^16.9.5",
@@ -446,4 +446,74 @@ export function aspectCache(imageUrl: string): Observable<number> { @@ -446,4 +446,74 @@ export function aspectCache(imageUrl: string): Observable<number> {
446 return aspect; 446 return aspect;
447 })) 447 }))
448 } 448 }
  449 +}
  450 +
  451 +
  452 +export function parseArray(input: any[]): any[] {
  453 + let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
  454 + console.log("alliases", alliases)
  455 + return alliases.map((alliasArray, dsIndex) =>
  456 + alliasArray[0].data.map((el, i) => {
  457 + const obj = {
  458 + aliasName: alliasArray[0]?.datasource?.aliasName,
  459 + $datasource: alliasArray[0]?.datasource,
  460 + dsIndex: dsIndex
  461 + };
  462 + alliasArray.forEach(el => {
  463 + obj[el?.dataKey?.label] = el?.data[i][1];
  464 + obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
  465 + if (el?.dataKey?.label == 'type') {
  466 + obj['deviceType'] = el?.data[0][1];
  467 + }
  468 + });
  469 + return obj;
  470 + })
  471 + );
  472 +}
  473 +
  474 +export function parseData(input: any[]): any[] {
  475 + return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {
  476 + const obj = {
  477 + aliasName: alliasArray[0]?.datasource?.aliasName,
  478 + entityName: alliasArray[0]?.datasource?.entityName,
  479 + $datasource: alliasArray[0]?.datasource,
  480 + dsIndex: i
  481 + };
  482 + alliasArray.forEach(el => {
  483 + obj[el?.dataKey?.label] = el?.data[0][1];
  484 + obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
  485 + if (el?.dataKey?.label == 'type') {
  486 + obj['deviceType'] = el?.data[0][1];
  487 + }
  488 + });
  489 + return obj;
  490 + });
  491 +}
  492 +
  493 +export function safeExecute(func: Function, params = []) {
  494 + let res = null;
  495 + if (func && typeof (func) == "function") {
  496 + try {
  497 + res = func(...params);
  498 + }
  499 + catch (err) {
  500 + console.error(err);
  501 + res = null;
  502 + }
  503 + }
  504 + return res;
  505 +}
  506 +
  507 +export function parseFunction(source: string, params: string[] = []): Function {
  508 + let res = null;
  509 + if (source?.length) {
  510 + try {
  511 + res = new Function(...params, source);
  512 + }
  513 + catch (err) {
  514 + console.error(err);
  515 + res = null;
  516 + }
  517 + }
  518 + return res;
449 } 519 }
@@ -14,10 +14,9 @@ import { @@ -14,10 +14,9 @@ import {
14 } from './schemes'; 14 } from './schemes';
15 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; 15 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
16 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; 16 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
17 -import { parseData, parseArray, parseFunction } from './maps-utils'; 17 +import { parseFunction, parseArray, parseData } from '@app/core/utils';
18 18
19 -export let TbMapWidgetV2: MapWidgetStaticInterface;  
20 -TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { 19 +export class MapWidgetController implements MapWidgetInterface {
21 20
22 map: LeafletMap; 21 map: LeafletMap;
23 provider: MapProviders; 22 provider: MapProviders;
@@ -201,6 +200,9 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { @@ -201,6 +200,9 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface {
201 } 200 }
202 } 201 }
203 202
  203 +export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController;
  204 +
  205 +
204 const providerSets = { 206 const providerSets = {
205 'openstreet-map': { 207 'openstreet-map': {
206 MapClass: OpenStreetMap, 208 MapClass: OpenStreetMap,
@@ -21,71 +21,3 @@ export function createTooltip(target, settings, targetArgs?) { @@ -21,71 +21,3 @@ export function createTooltip(target, settings, targetArgs?) {
21 dsIndex: settings.dsIndex 21 dsIndex: settings.dsIndex
22 }; 22 };
23 } 23 }
24 -  
25 -export function parseArray(input: any[]): any[] {  
26 - let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();  
27 - return alliases.map((alliasArray, dsIndex) =>  
28 - alliasArray[0].data.map((el, i) => {  
29 - const obj = {  
30 - aliasName: alliasArray[0]?.datasource?.aliasName,  
31 - $datasource: alliasArray[0]?.datasource,  
32 - dsIndex: dsIndex  
33 - };  
34 - alliasArray.forEach(el => {  
35 - obj[el?.dataKey?.label] = el?.data[i][1];  
36 - obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];  
37 - if (el?.dataKey?.label == 'type') {  
38 - obj['deviceType'] = el?.data[0][1];  
39 - }  
40 - });  
41 - return obj;  
42 - })  
43 - );  
44 -}  
45 -  
46 -export function parseData(input: any[]): any[] {  
47 - return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {  
48 - const obj = {  
49 - aliasName: alliasArray[0]?.datasource?.aliasName,  
50 - entityName: alliasArray[0]?.datasource?.entityName,  
51 - $datasource: alliasArray[0]?.datasource,  
52 - dsIndex: i  
53 - };  
54 - alliasArray.forEach(el => {  
55 - obj[el?.dataKey?.label] = el?.data[0][1];  
56 - obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];  
57 - if (el?.dataKey?.label == 'type') {  
58 - obj['deviceType'] = el?.data[0][1];  
59 - }  
60 - });  
61 - return obj;  
62 - });  
63 -}  
64 -  
65 -export function safeExecute(func: Function, params = []) {  
66 - let res = null;  
67 - if (func && typeof (func) == "function") {  
68 - try {  
69 - res = func(...params);  
70 - }  
71 - catch (err) {  
72 - console.error(err);  
73 - res = null;  
74 - }  
75 - }  
76 - return res;  
77 -}  
78 -  
79 -export function parseFunction(source: string, params: string[] = []): Function {  
80 - let res = null;  
81 - if (source?.length) {  
82 - try {  
83 - res = new Function(...params, source);  
84 - }  
85 - catch (err) {  
86 - console.error(err);  
87 - res = null;  
88 - }  
89 - }  
90 - return res;  
91 -}  
1 import L from 'leaflet'; 1 import L from 'leaflet';
2 -import { createTooltip, safeExecute, parseFunction } from './maps-utils';  
3 import { MarkerSettings } from './map-models'; 2 import { MarkerSettings } from './map-models';
4 -import { aspectCache } from '@app/core/utils'; 3 +import { aspectCache, safeExecute, parseFunction } from '@app/core/utils';
  4 +import { createTooltip } from './maps-utils';
5 5
6 export class Marker { 6 export class Marker {
7 7
1 import L from 'leaflet'; 1 import L from 'leaflet';
2 -import { safeExecute } from './maps-utils'; 2 +import { safeExecute } from '@app/core/utils';
3 3
4 export class Polyline { 4 export class Polyline {
5 5
@@ -14,7 +14,7 @@ export class GoogleMap extends LeafletMap { @@ -14,7 +14,7 @@ export class GoogleMap extends LeafletMap {
14 this.loadGoogle(() => { 14 this.loadGoogle(() => {
15 const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); 15 const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
16 var roads = (L.gridLayer as any).googleMutant({ 16 var roads = (L.gridLayer as any).googleMutant({
17 - type: options?.gmDefaultMapType || 'roadmap' // valid values are 'roadmap', 'satellite', 'terrain' and 'hybrid' 17 + type: options?.gmDefaultMapType || 'roadmap'
18 }).addTo(map); 18 }).addTo(map);
19 super.setMap(map); 19 super.setMap(map);
20 }, options.credentials.apiKey); 20 }, options.credentials.apiKey);
  1 +<div class="map" #map ></div>
  2 +<tb-history-selector [settings]="ctx.settings"></tb-history-selector>
  1 +
  2 +
  3 +.map{
  4 + width: 100%;
  5 + height: calc(100% - 100px);
  6 +}
  1 +import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
  2 +import { MapWidgetController } from '../lib/maps/map-widget2';
  3 +import { MapProviders } from '../lib/maps/map-models';
  4 +import { parseArray } from '@app/core/utils';
  5 +
  6 +@Component({
  7 + selector: 'trip-animation',
  8 + templateUrl: './trip-animation.component.html',
  9 + styleUrls: ['./trip-animation.component.scss']
  10 +})
  11 +export class TripAnimationComponent implements OnInit, AfterViewInit {
  12 +
  13 + @Input() ctx;
  14 +
  15 + @ViewChild('map') mapContainer;
  16 +
  17 + mapWidget: MapWidgetController;
  18 + historicalData
  19 +
  20 + constructor() { }
  21 +
  22 + ngOnInit(): void {
  23 + console.log(this.ctx);
  24 + this.historicalData = parseArray(this.ctx.data);
  25 + console.log("TripAnimationComponent -> ngOnInit -> this.historicalData",this.ctx.data, this.historicalData)
  26 + }
  27 +
  28 + ngAfterViewInit() {
  29 + this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
  30 + this.mapWidget.data
  31 + }
  32 +}
@@ -31,6 +31,7 @@ import { @@ -31,6 +31,7 @@ import {
31 DateRangeNavigatorWidgetComponent 31 DateRangeNavigatorWidgetComponent
32 } from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component'; 32 } from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component';
33 import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component'; 33 import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component';
  34 +import { TripAnimationComponent } from './trip-animation/trip-animation.component';
34 35
35 @NgModule({ 36 @NgModule({
36 declarations: 37 declarations:
@@ -43,7 +44,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon @@ -43,7 +44,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
43 EntitiesHierarchyWidgetComponent, 44 EntitiesHierarchyWidgetComponent,
44 DateRangeNavigatorWidgetComponent, 45 DateRangeNavigatorWidgetComponent,
45 DateRangeNavigatorPanelComponent, 46 DateRangeNavigatorPanelComponent,
46 - MultipleInputWidgetComponent 47 + MultipleInputWidgetComponent,
  48 + TripAnimationComponent
47 ], 49 ],
48 imports: [ 50 imports: [
49 CommonModule, 51 CommonModule,
@@ -58,7 +60,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon @@ -58,7 +60,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
58 EntitiesHierarchyWidgetComponent, 60 EntitiesHierarchyWidgetComponent,
59 RpcWidgetsModule, 61 RpcWidgetsModule,
60 DateRangeNavigatorWidgetComponent, 62 DateRangeNavigatorWidgetComponent,
61 - MultipleInputWidgetComponent 63 + MultipleInputWidgetComponent,
  64 + TripAnimationComponent
62 ], 65 ],
63 providers: [ 66 providers: [
64 CustomDialogService 67 CustomDialogService
  1 +<div class="trip-animation-control-panel">
  2 + <div>
  3 + <button mat-icon-button class="mat-icon-button" aria-label="Start" ng-click="moveStart()">
  4 + <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">fast_rewind</mat-icon>
  5 + </button>
  6 + <button mat-icon-button class="mat-icon-button" aria-label="Previous" ng-click="movePrev()">
  7 + <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_previous</mat-icon>
  8 + </button>
  9 + <!-- <mat-slider ng-model="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="recalculateTrips()">
  10 + </mat-slider>-->
  11 + <button mat-icon-button class="mat-icon-button" aria-label="Next" ng-click="moveNext()">
  12 + <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_next</mat-icon>
  13 + </button>
  14 + <button mat-icon-button class="mat-icon-button" aria-label="End" ng-click="moveEnd()">
  15 + <mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">fast_forward</mat-icon>
  16 + </button>
  17 + <button mat-icon-button class="mat-icon-button" aria-label="Play" ng-click="playMove(true)"
  18 + ng-disabled="isPlaying">
  19 + <mat-icon class="material-icons"
  20 + ng-style="{'color': isPlaying ? staticSettings.disabledButtonColor : staticSettings.buttonColor}">
  21 + play_circle_outline
  22 + </mat-icon>
  23 + </button>
  24 + <!-- <mat-select ng-model="speed" aria-label="Speed selector">
  25 + <mat-option ng-value="speed" flex="1" ng-repeat="speed in speeds track by $index">{{ speed}}
  26 + </mat-option>
  27 + </mat-select>-->
  28 + <button mat-icon-button class="mat-icon-button" aria-label="Stop playing" ng-click="stopPlay()"
  29 + ng-disabled="!isPlaying">
  30 + <mat-icon class="material-icons"
  31 + ng-style="{'color': isPlaying ? staticSettings.buttonColor : staticSettings.disabledButtonColor}">
  32 + pause_circle_outline
  33 + </mat-icon>
  34 + </button>
  35 + </div>
  36 + <div class="panel-timer">
  37 + <span *ngIf="animationTime">{{ animationTime | date:'medium'}}</span>
  38 + <span *ngIf="!animationTime">{{ "widget.no-data-found" | translate}}</span>
  39 + </div>
  1 +/**
  2 + * Copyright © 2016-2020 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 + .trip-animation-widget {
  17 +
  18 + position: relative;
  19 + width: 100%;
  20 + height: 100%;
  21 + font-size: 16px;
  22 + line-height: 24px;
  23 +
  24 + .trip-animation-label-container {
  25 + height: 24px;
  26 + }
  27 +
  28 + .trip-animation-container {
  29 + position: relative;
  30 + z-index: 1;
  31 + flex: 1;
  32 + width: 100%;
  33 +
  34 + #trip-animation-map {
  35 + z-index: 1;
  36 + width: 100%;
  37 + height: 100%;
  38 +
  39 + .pointsLayerMarkerIcon {
  40 + border-radius: 50%;
  41 + }
  42 + }
  43 +
  44 + .trip-animation-info-panel {
  45 + position: absolute;
  46 + top: 0;
  47 + right: 0;
  48 + z-index: 2;
  49 + pointer-events: none;
  50 +
  51 + .md-button {
  52 + top: 0;
  53 + left: 0;
  54 + width: 32px;
  55 + min-width: 32px;
  56 + height: 32px;
  57 + min-height: 32px;
  58 + padding: 0 0 2px;
  59 + margin: 2px;
  60 + line-height: 24px;
  61 +
  62 + ng-md-icon {
  63 + width: 24px;
  64 + height: 24px;
  65 +
  66 + svg {
  67 + width: inherit;
  68 + height: inherit;
  69 + }
  70 + }
  71 + }
  72 + }
  73 +
  74 + .trip-animation-tooltip {
  75 + position: absolute;
  76 + top: 38px;
  77 + right: 0;
  78 + z-index: 2;
  79 + padding: 10px;
  80 + background-color: #fff;
  81 + transition: .3s ease-in-out;
  82 +
  83 + &-hidden {
  84 + transform: translateX(110%);
  85 + }
  86 + }
  87 + }
  88 +
  89 + .trip-animation-control-panel {
  90 + position: relative;
  91 + box-sizing: border-box;
  92 + width: 100%;
  93 + padding-bottom: 16px;
  94 + padding-left: 10px;
  95 +
  96 + md-slider-container {
  97 + md-slider {
  98 + min-width: 80px;
  99 + }
  100 +
  101 + button.md-button.md-icon-button {
  102 + width: 44px;
  103 + min-width: 44px;
  104 + height: 44px;
  105 + min-height: 44px;
  106 + margin: 0;
  107 + line-height: 28px;
  108 +
  109 + md-icon {
  110 + width: 28px;
  111 + height: 28px;
  112 + font-size: 28px;
  113 +
  114 + svg {
  115 + width: inherit;
  116 + height: inherit;
  117 + }
  118 + }
  119 + }
  120 +
  121 + md-select {
  122 + margin: 0;
  123 + }
  124 + }
  125 +
  126 + .panel-timer {
  127 + max-width: none;
  128 + padding-right: 250px;
  129 + padding-left: 90px;
  130 + margin-top: -20px;
  131 + font-size: 12px;
  132 + font-weight: 500;
  133 + text-align: center;
  134 + }
  135 + }
  136 + }
  137 +
  1 +import { Component, OnInit, Input } from '@angular/core';
  2 +
  3 +@Component({
  4 + selector: 'tb-history-selector',
  5 + templateUrl: './history-selector.component.html',
  6 + styleUrls: ['./history-selector.component.scss']
  7 +})
  8 +export class HistorySelectorComponent implements OnInit {
  9 +
  10 + @Input() settings
  11 +
  12 + animationTime
  13 +
  14 +
  15 + constructor() { }
  16 +
  17 + ngOnInit(): void {
  18 + console.log(this.settings);
  19 +
  20 + }
  21 +
  22 +}
@@ -127,6 +127,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component'; @@ -127,6 +127,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component';
127 import { LedLightComponent } from '@shared/components/led-light.component'; 127 import { LedLightComponent } from '@shared/components/led-light.component';
128 import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; 128 import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive";
129 import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; 129 import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component";
  130 +import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
130 131
131 @NgModule({ 132 @NgModule({
132 providers: [ 133 providers: [
@@ -209,7 +210,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob @@ -209,7 +210,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob
209 TbJsonPipe, 210 TbJsonPipe,
210 KeyboardShortcutPipe, 211 KeyboardShortcutPipe,
211 TbJsonToStringDirective, 212 TbJsonToStringDirective,
212 - JsonObjectEditDialogComponent 213 + JsonObjectEditDialogComponent,
  214 + HistorySelectorComponent
213 ], 215 ],
214 imports: [ 216 imports: [
215 CommonModule, 217 CommonModule,
@@ -367,7 +369,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob @@ -367,7 +369,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob
367 TbJsonPipe, 369 TbJsonPipe,
368 KeyboardShortcutPipe, 370 KeyboardShortcutPipe,
369 TranslateModule, 371 TranslateModule,
370 - JsonObjectEditDialogComponent 372 + JsonObjectEditDialogComponent,
  373 + HistorySelectorComponent
371 ] 374 ]
372 }) 375 })
373 export class SharedModule { } 376 export class SharedModule { }