Showing
15 changed files
with
328 additions
and
80 deletions
... | ... | @@ -38,7 +38,6 @@ |
38 | 38 | "@ngx-share/core": "^7.1.4", |
39 | 39 | "@ngx-translate/core": "^12.1.1", |
40 | 40 | "@ngx-translate/http-loader": "^4.0.0", |
41 | - "@types/lodash": "^4.14.149", | |
42 | 41 | "ace-builds": "^1.4.8", |
43 | 42 | "angular-gridster2": "^9.0.1", |
44 | 43 | "angular2-hotkeys": "^2.1.5", |
... | ... | @@ -105,6 +104,7 @@ |
105 | 104 | "@types/js-beautify": "^1.8.1", |
106 | 105 | "@types/jstree": "^3.3.39", |
107 | 106 | "@types/leaflet": "^1.5.9", |
107 | + "@types/lodash": "^4.14.149", | |
108 | 108 | "@types/raphael": "^2.1.30", |
109 | 109 | "@types/react": "^16.9.20", |
110 | 110 | "@types/react-dom": "^16.9.5", | ... | ... |
... | ... | @@ -446,4 +446,74 @@ export function aspectCache(imageUrl: string): Observable<number> { |
446 | 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 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -14,10 +14,9 @@ import { |
14 | 14 | } from './schemes'; |
15 | 15 | import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; |
16 | 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 | 21 | map: LeafletMap; |
23 | 22 | provider: MapProviders; |
... | ... | @@ -201,6 +200,9 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface { |
201 | 200 | } |
202 | 201 | } |
203 | 202 | |
203 | +export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController; | |
204 | + | |
205 | + | |
204 | 206 | const providerSets = { |
205 | 207 | 'openstreet-map': { |
206 | 208 | MapClass: OpenStreetMap, | ... | ... |
... | ... | @@ -21,71 +21,3 @@ export function createTooltip(target, settings, targetArgs?) { |
21 | 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 | -} | |
\ No newline at end of file | ... | ... |
1 | 1 | import L from 'leaflet'; |
2 | -import { createTooltip, safeExecute, parseFunction } from './maps-utils'; | |
3 | 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 | 6 | export class Marker { |
7 | 7 | ... | ... |
... | ... | @@ -14,7 +14,7 @@ export class GoogleMap extends LeafletMap { |
14 | 14 | this.loadGoogle(() => { |
15 | 15 | const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); |
16 | 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 | 18 | }).addTo(map); |
19 | 19 | super.setMap(map); |
20 | 20 | }, options.credentials.apiKey); | ... | ... |
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 | 31 | DateRangeNavigatorWidgetComponent |
32 | 32 | } from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component'; |
33 | 33 | import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component'; |
34 | +import { TripAnimationComponent } from './trip-animation/trip-animation.component'; | |
34 | 35 | |
35 | 36 | @NgModule({ |
36 | 37 | declarations: |
... | ... | @@ -43,7 +44,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon |
43 | 44 | EntitiesHierarchyWidgetComponent, |
44 | 45 | DateRangeNavigatorWidgetComponent, |
45 | 46 | DateRangeNavigatorPanelComponent, |
46 | - MultipleInputWidgetComponent | |
47 | + MultipleInputWidgetComponent, | |
48 | + TripAnimationComponent | |
47 | 49 | ], |
48 | 50 | imports: [ |
49 | 51 | CommonModule, |
... | ... | @@ -58,7 +60,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon |
58 | 60 | EntitiesHierarchyWidgetComponent, |
59 | 61 | RpcWidgetsModule, |
60 | 62 | DateRangeNavigatorWidgetComponent, |
61 | - MultipleInputWidgetComponent | |
63 | + MultipleInputWidgetComponent, | |
64 | + TripAnimationComponent | |
62 | 65 | ], |
63 | 66 | providers: [ |
64 | 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> | |
\ No newline at end of file | ... | ... |
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 | + | |
\ No newline at end of file | ... | ... |
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 | 127 | import { LedLightComponent } from '@shared/components/led-light.component'; |
128 | 128 | import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; |
129 | 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 | 132 | @NgModule({ |
132 | 133 | providers: [ |
... | ... | @@ -209,7 +210,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob |
209 | 210 | TbJsonPipe, |
210 | 211 | KeyboardShortcutPipe, |
211 | 212 | TbJsonToStringDirective, |
212 | - JsonObjectEditDialogComponent | |
213 | + JsonObjectEditDialogComponent, | |
214 | + HistorySelectorComponent | |
213 | 215 | ], |
214 | 216 | imports: [ |
215 | 217 | CommonModule, |
... | ... | @@ -367,7 +369,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob |
367 | 369 | TbJsonPipe, |
368 | 370 | KeyboardShortcutPipe, |
369 | 371 | TranslateModule, |
370 | - JsonObjectEditDialogComponent | |
372 | + JsonObjectEditDialogComponent, | |
373 | + HistorySelectorComponent | |
371 | 374 | ] |
372 | 375 | }) |
373 | 376 | export class SharedModule { } | ... | ... |