WIP on trip animation widget

This commit is contained in:
Artem Halushko 2020-03-06 19:22:47 +02:00
parent 5d33da4b09
commit be770518d7
15 changed files with 328 additions and 80 deletions

View File

@ -38,7 +38,6 @@
"@ngx-share/core": "^7.1.4",
"@ngx-translate/core": "^12.1.1",
"@ngx-translate/http-loader": "^4.0.0",
"@types/lodash": "^4.14.149",
"ace-builds": "^1.4.8",
"angular-gridster2": "^9.0.1",
"angular2-hotkeys": "^2.1.5",
@ -105,6 +104,7 @@
"@types/js-beautify": "^1.8.1",
"@types/jstree": "^3.3.39",
"@types/leaflet": "^1.5.9",
"@types/lodash": "^4.14.149",
"@types/raphael": "^2.1.30",
"@types/react": "^16.9.20",
"@types/react-dom": "^16.9.5",

View File

@ -447,3 +447,73 @@ export function aspectCache(imageUrl: string): Observable<number> {
}))
}
}
export function parseArray(input: any[]): any[] {
let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
console.log("alliases", alliases)
return alliases.map((alliasArray, dsIndex) =>
alliasArray[0].data.map((el, i) => {
const obj = {
aliasName: alliasArray[0]?.datasource?.aliasName,
$datasource: alliasArray[0]?.datasource,
dsIndex: dsIndex
};
alliasArray.forEach(el => {
obj[el?.dataKey?.label] = el?.data[i][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label == 'type') {
obj['deviceType'] = el?.data[0][1];
}
});
return obj;
})
);
}
export function parseData(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {
const obj = {
aliasName: alliasArray[0]?.datasource?.aliasName,
entityName: alliasArray[0]?.datasource?.entityName,
$datasource: alliasArray[0]?.datasource,
dsIndex: i
};
alliasArray.forEach(el => {
obj[el?.dataKey?.label] = el?.data[0][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label == 'type') {
obj['deviceType'] = el?.data[0][1];
}
});
return obj;
});
}
export function safeExecute(func: Function, params = []) {
let res = null;
if (func && typeof (func) == "function") {
try {
res = func(...params);
}
catch (err) {
console.error(err);
res = null;
}
}
return res;
}
export function parseFunction(source: string, params: string[] = []): Function {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
console.error(err);
res = null;
}
}
return res;
}

View File

@ -14,10 +14,9 @@ import {
} from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
import { parseData, parseArray, parseFunction } from './maps-utils';
import { parseFunction, parseArray, parseData } from '@app/core/utils';
export let TbMapWidgetV2: MapWidgetStaticInterface;
TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface {
export class MapWidgetController implements MapWidgetInterface {
map: LeafletMap;
provider: MapProviders;
@ -201,6 +200,9 @@ TbMapWidgetV2 = class TbMapWidgetV2 implements MapWidgetInterface {
}
}
export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController;
const providerSets = {
'openstreet-map': {
MapClass: OpenStreetMap,

View File

@ -21,71 +21,3 @@ export function createTooltip(target, settings, targetArgs?) {
dsIndex: settings.dsIndex
};
}
export function parseArray(input: any[]): any[] {
let alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
return alliases.map((alliasArray, dsIndex) =>
alliasArray[0].data.map((el, i) => {
const obj = {
aliasName: alliasArray[0]?.datasource?.aliasName,
$datasource: alliasArray[0]?.datasource,
dsIndex: dsIndex
};
alliasArray.forEach(el => {
obj[el?.dataKey?.label] = el?.data[i][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label == 'type') {
obj['deviceType'] = el?.data[0][1];
}
});
return obj;
})
);
}
export function parseData(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {
const obj = {
aliasName: alliasArray[0]?.datasource?.aliasName,
entityName: alliasArray[0]?.datasource?.entityName,
$datasource: alliasArray[0]?.datasource,
dsIndex: i
};
alliasArray.forEach(el => {
obj[el?.dataKey?.label] = el?.data[0][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label == 'type') {
obj['deviceType'] = el?.data[0][1];
}
});
return obj;
});
}
export function safeExecute(func: Function, params = []) {
let res = null;
if (func && typeof (func) == "function") {
try {
res = func(...params);
}
catch (err) {
console.error(err);
res = null;
}
}
return res;
}
export function parseFunction(source: string, params: string[] = []): Function {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
console.error(err);
res = null;
}
}
return res;
}

View File

@ -1,7 +1,7 @@
import L from 'leaflet';
import { createTooltip, safeExecute, parseFunction } from './maps-utils';
import { MarkerSettings } from './map-models';
import { aspectCache } from '@app/core/utils';
import { aspectCache, safeExecute, parseFunction } from '@app/core/utils';
import { createTooltip } from './maps-utils';
export class Marker {

View File

@ -1,5 +1,5 @@
import L from 'leaflet';
import { safeExecute } from './maps-utils';
import { safeExecute } from '@app/core/utils';
export class Polyline {

View File

@ -14,7 +14,7 @@ export class GoogleMap extends LeafletMap {
this.loadGoogle(() => {
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
var roads = (L.gridLayer as any).googleMutant({
type: options?.gmDefaultMapType || 'roadmap' // valid values are 'roadmap', 'satellite', 'terrain' and 'hybrid'
type: options?.gmDefaultMapType || 'roadmap'
}).addTo(map);
super.setMap(map);
}, options.credentials.apiKey);

View File

@ -0,0 +1,2 @@
<div class="map" #map ></div>
<tb-history-selector [settings]="ctx.settings"></tb-history-selector>

View File

@ -0,0 +1,6 @@
.map{
width: 100%;
height: calc(100% - 100px);
}

View File

@ -0,0 +1,32 @@
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
import { MapWidgetController } from '../lib/maps/map-widget2';
import { MapProviders } from '../lib/maps/map-models';
import { parseArray } from '@app/core/utils';
@Component({
selector: 'trip-animation',
templateUrl: './trip-animation.component.html',
styleUrls: ['./trip-animation.component.scss']
})
export class TripAnimationComponent implements OnInit, AfterViewInit {
@Input() ctx;
@ViewChild('map') mapContainer;
mapWidget: MapWidgetController;
historicalData
constructor() { }
ngOnInit(): void {
console.log(this.ctx);
this.historicalData = parseArray(this.ctx.data);
console.log("TripAnimationComponent -> ngOnInit -> this.historicalData",this.ctx.data, this.historicalData)
}
ngAfterViewInit() {
this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement);
this.mapWidget.data
}
}

View File

@ -31,6 +31,7 @@ import {
DateRangeNavigatorWidgetComponent
} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component';
import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component';
import { TripAnimationComponent } from './trip-animation/trip-animation.component';
@NgModule({
declarations:
@ -43,7 +44,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
EntitiesHierarchyWidgetComponent,
DateRangeNavigatorWidgetComponent,
DateRangeNavigatorPanelComponent,
MultipleInputWidgetComponent
MultipleInputWidgetComponent,
TripAnimationComponent
],
imports: [
CommonModule,
@ -58,7 +60,8 @@ import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.compon
EntitiesHierarchyWidgetComponent,
RpcWidgetsModule,
DateRangeNavigatorWidgetComponent,
MultipleInputWidgetComponent
MultipleInputWidgetComponent,
TripAnimationComponent
],
providers: [
CustomDialogService

View File

@ -0,0 +1,39 @@
<div class="trip-animation-control-panel">
<div>
<button mat-icon-button class="mat-icon-button" aria-label="Start" ng-click="moveStart()">
<mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">fast_rewind</mat-icon>
</button>
<button mat-icon-button class="mat-icon-button" aria-label="Previous" ng-click="movePrev()">
<mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_previous</mat-icon>
</button>
<!-- <mat-slider ng-model="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="recalculateTrips()">
</mat-slider>-->
<button mat-icon-button class="mat-icon-button" aria-label="Next" ng-click="moveNext()">
<mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">skip_next</mat-icon>
</button>
<button mat-icon-button class="mat-icon-button" aria-label="End" ng-click="moveEnd()">
<mat-icon class="material-icons" ng-style="{'color': staticSettings.buttonColor}">fast_forward</mat-icon>
</button>
<button mat-icon-button class="mat-icon-button" aria-label="Play" ng-click="playMove(true)"
ng-disabled="isPlaying">
<mat-icon class="material-icons"
ng-style="{'color': isPlaying ? staticSettings.disabledButtonColor : staticSettings.buttonColor}">
play_circle_outline
</mat-icon>
</button>
<!-- <mat-select ng-model="speed" aria-label="Speed selector">
<mat-option ng-value="speed" flex="1" ng-repeat="speed in speeds track by $index">{{ speed}}
</mat-option>
</mat-select>-->
<button mat-icon-button class="mat-icon-button" aria-label="Stop playing" ng-click="stopPlay()"
ng-disabled="!isPlaying">
<mat-icon class="material-icons"
ng-style="{'color': isPlaying ? staticSettings.buttonColor : staticSettings.disabledButtonColor}">
pause_circle_outline
</mat-icon>
</button>
</div>
<div class="panel-timer">
<span *ngIf="animationTime">{{ animationTime | date:'medium'}}</span>
<span *ngIf="!animationTime">{{ "widget.no-data-found" | translate}}</span>
</div>

View File

@ -0,0 +1,137 @@
/**
* Copyright © 2016-2020 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.
*/
.trip-animation-widget {
position: relative;
width: 100%;
height: 100%;
font-size: 16px;
line-height: 24px;
.trip-animation-label-container {
height: 24px;
}
.trip-animation-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
#trip-animation-map {
z-index: 1;
width: 100%;
height: 100%;
.pointsLayerMarkerIcon {
border-radius: 50%;
}
}
.trip-animation-info-panel {
position: absolute;
top: 0;
right: 0;
z-index: 2;
pointer-events: none;
.md-button {
top: 0;
left: 0;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
padding: 0 0 2px;
margin: 2px;
line-height: 24px;
ng-md-icon {
width: 24px;
height: 24px;
svg {
width: inherit;
height: inherit;
}
}
}
}
.trip-animation-tooltip {
position: absolute;
top: 38px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(110%);
}
}
}
.trip-animation-control-panel {
position: relative;
box-sizing: border-box;
width: 100%;
padding-bottom: 16px;
padding-left: 10px;
md-slider-container {
md-slider {
min-width: 80px;
}
button.md-button.md-icon-button {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
margin: 0;
line-height: 28px;
md-icon {
width: 28px;
height: 28px;
font-size: 28px;
svg {
width: inherit;
height: inherit;
}
}
}
md-select {
margin: 0;
}
}
.panel-timer {
max-width: none;
padding-right: 250px;
padding-left: 90px;
margin-top: -20px;
font-size: 12px;
font-weight: 500;
text-align: center;
}
}
}

View File

@ -0,0 +1,22 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'tb-history-selector',
templateUrl: './history-selector.component.html',
styleUrls: ['./history-selector.component.scss']
})
export class HistorySelectorComponent implements OnInit {
@Input() settings
animationTime
constructor() { }
ngOnInit(): void {
console.log(this.settings);
}
}

View File

@ -127,6 +127,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component';
import { LedLightComponent } from '@shared/components/led-light.component';
import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive";
import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component";
import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
@NgModule({
providers: [
@ -209,7 +210,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob
TbJsonPipe,
KeyboardShortcutPipe,
TbJsonToStringDirective,
JsonObjectEditDialogComponent
JsonObjectEditDialogComponent,
HistorySelectorComponent
],
imports: [
CommonModule,
@ -367,7 +369,8 @@ import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-ob
TbJsonPipe,
KeyboardShortcutPipe,
TranslateModule,
JsonObjectEditDialogComponent
JsonObjectEditDialogComponent,
HistorySelectorComponent
]
})
export class SharedModule { }