Commit dcd00e587a53bbef34000ff0e3b304c03f469adb

Authored by Igor Kulikov
2 parents 6131000c 13e6b10b

Merge branch 'master' of github.com:thingsboard/thingsboard

... ... @@ -20,12 +20,12 @@ import 'leaflet-providers';
20 20 import 'leaflet.markercluster/dist/leaflet.markercluster';
21 21
22 22 import {
23   - FormattedData,
24   - MapSettings,
25   - MarkerSettings,
26   - PolygonSettings,
27   - PolylineSettings,
28   - UnitedMapSettings
  23 + FormattedData,
  24 + MapSettings,
  25 + MarkerSettings,
  26 + PolygonSettings,
  27 + PolylineSettings,
  28 + UnitedMapSettings
29 29 } from './map-models';
30 30 import { Marker } from './markers';
31 31 import { BehaviorSubject, Observable } from 'rxjs';
... ... @@ -158,9 +158,9 @@ export default abstract class LeafletMap {
158 158 this.map = map;
159 159 if (this.options.useDefaultCenterPosition) {
160 160 this.map.panTo(this.options.defaultCenterPosition);
161   - this.bounds = map.getBounds();
  161 + this.bounds = map.getBounds();
162 162 }
163   - else this.bounds = new L.LatLngBounds(null, null);
  163 + else this.bounds = new L.LatLngBounds(null, null);
164 164 if (this.options.draggableMarker) {
165 165 this.addMarkerControl();
166 166 }
... ... @@ -244,7 +244,7 @@ export default abstract class LeafletMap {
244 244 }
245 245
246 246 // Markers
247   - updateMarkers(markersData) {
  247 + updateMarkers(markersData, callback?) {
248 248 markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => {
249 249 if (data.rotationAngle || data.rotationAngle === 0) {
250 250 const currentImage = this.options.useMarkerImageFunction ?
... ... @@ -265,7 +265,7 @@ export default abstract class LeafletMap {
265 265 this.updateMarker(data.entityName, data, markersData, this.options)
266 266 }
267 267 else {
268   - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings);
  268 + this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, callback);
269 269 }
270 270 });
271 271 this.markersData = markersData;
... ... @@ -276,9 +276,11 @@ export default abstract class LeafletMap {
276 276 this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
277 277 }
278 278
279   - private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
  279 + private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, callback?) {
280 280 this.ready$.subscribe(() => {
281 281 const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
  282 + if (callback)
  283 + newMarker.leafletMarker.on('click', () => { callback(data, true) });
282 284 if (this.bounds)
283 285 this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
284 286 this.markers.set(key, newMarker);
... ... @@ -344,41 +346,58 @@ export default abstract class LeafletMap {
344 346 // Polyline
345 347
346 348 updatePolylines(polyData: FormattedData[][], data?: FormattedData) {
347   - polyData.forEach((dataSource) => {
348   - if (dataSource.length) {
349   - data = data || dataSource[0];
350   - if (this.polylines.get(data.$datasource.entityName)) {
  349 + polyData.forEach((dataSource: FormattedData[]) => {
  350 + data = data || dataSource[0];
  351 + if (dataSource.length && data.entityName === dataSource[0].entityName) {
  352 + if (this.polylines.get(data.entityName)) {
351 353 this.updatePolyline(data, dataSource, this.options);
352 354 }
353 355 else {
354 356 this.createPolyline(data, dataSource, this.options);
355 357 }
356 358 }
  359 + else {
  360 + if (data)
  361 + this.removePolyline(dataSource[0]?.entityName)
  362 + }
357 363 })
358 364 }
359 365
360 366 createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
361 367 this.ready$.subscribe(() => {
362 368 const poly = new Polyline(this.map,
363   - dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
  369 + dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
364 370 const bounds = poly.leafletPoly.getBounds();
365 371 this.fitBounds(bounds);
366   - this.polylines.set(data.$datasource.entityName, poly);
  372 + this.polylines.set(data.entityName, poly);
367 373 });
368 374 }
369 375
370 376 updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
371 377 this.ready$.subscribe(() => {
372   - const poly = this.polylines.get(data.$datasource.entityName);
  378 + const poly = this.polylines.get(data.entityName);
  379 + const oldBounds = poly.leafletPoly.getBounds();
373 380 poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
  381 + const newBounds = poly.leafletPoly.getBounds();
  382 + if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
  383 + this.fitBounds(newBounds);
  384 + }
374 385 });
375 386 }
376 387
  388 + removePolyline(name: string) {
  389 + const poly = this.polylines.get(name);
  390 + if (poly) {
  391 + this.map.removeLayer(poly.leafletPoly);
  392 + this.polylines.delete(name);
  393 + }
  394 + }
  395 +
377 396 // Polygon
378 397
379 398 updatePolygons(polyData: FormattedData[]) {
380 399 polyData.forEach((data: FormattedData) => {
381   - if (data.hasOwnProperty(this.options.polygonKeyName)) {
  400 + if (data && data.hasOwnProperty(this.options.polygonKeyName)) {
382 401 if (typeof (data[this.options.polygonKeyName]) === 'string') {
383 402 data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]) as LatLngTuple[];
384 403 }
... ... @@ -403,8 +422,13 @@ export default abstract class LeafletMap {
403 422
404 423 updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
405 424 this.ready$.subscribe(() => {
406   - const poly = this.polygons.get(polyData.$datasource.entityName);
  425 + const poly = this.polygons.get(polyData.entityName);
  426 + const oldBounds = poly.leafletPoly.getBounds();
407 427 poly.updatePolygon(polyData, dataSources, settings);
  428 + const newBounds = poly.leafletPoly.getBounds();
  429 + if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
  430 + this.fitBounds(newBounds);
  431 + }
408 432 });
409 433 }
410 434 }
... ...
... ... @@ -26,6 +26,7 @@ import {
26 26
27 27 export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
28 28 export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
  29 +export type GetTooltip= (point: FormattedData, setTooltip?: boolean) => string;
29 30
30 31 export type MapSettings = {
31 32 draggableMarker: boolean;
... ...
... ... @@ -69,7 +69,6 @@ export class MapWidgetController implements MapWidgetInterface {
69 69 this.settings.markerClick = this.getDescriptors('markerClick');
70 70 this.settings.polygonClick = this.getDescriptors('polygonClick');
71 71
72   - // this.settings.
73 72 const MapClass = providerSets[this.provider]?.MapClass;
74 73 if (!MapClass) {
75 74 return;
... ...
... ... @@ -258,7 +258,7 @@ export function parseArray(input: any[]): any[] {
258 258 time: el[0],
259 259 deviceType: null
260 260 };
261   - entityArray.filter(e => e.data.length).forEach(entity => {
  261 + entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {
262 262 obj[entity?.dataKey?.label] = entity?.data[i][1];
263 263 obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
264 264 if (entity?.dataKey?.label === 'type') {
... ...
... ... @@ -65,7 +65,6 @@ export class Polyline {
65 65 this.dataSources = dataSources;
66 66 this.leafletPoly.setLatLngs(locations);
67 67 this.leafletPoly.setStyle(this.getPolyStyle(settings));
68   - // this.setPolylineLatLngs(data);
69 68 if (this.polylineDecorator)
70 69 this.polylineDecorator.setPaths(this.leafletPoly);
71 70 }
... ... @@ -92,8 +91,4 @@ export class Polyline {
92 91 getPolylineLatLngs() {
93 92 return this.leafletPoly.getLatLngs();
94 93 }
95   -
96   - setPolylineLatLngs(latLngs) {
97   - this.leafletPoly.setLatLngs(latLngs);
98   - }
99 94 }
... ...
... ... @@ -56,6 +56,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
56 56 historicalData: FormattedData[][];
57 57 normalizationStep: number;
58 58 interpolatedTimeData = [];
  59 + intervals = [];
59 60 widgetConfig: WidgetConfig;
60 61 settings;
61 62 mainTooltip = '';
... ... @@ -68,6 +69,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
68 69 maxTimeFormat: string;
69 70 anchors: number[] = [];
70 71 useAnchors: boolean;
  72 + currentTime: number;
71 73
72 74 static getSettingsSchema(): JsonSettingsSchema {
73 75 const schema = initSchema();
... ... @@ -99,15 +101,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
99 101 this.settings.fitMapBounds = true;
100 102 this.normalizationStep = this.settings.normalizationStep;
101 103 const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
102   - if (subscription) {
103   - subscription.callbacks.onDataUpdated = () => {
104   - this.historicalData = parseArray(this.ctx.data);
  104 + if (subscription) subscription.callbacks.onDataUpdated = () => {
  105 + this.historicalData = parseArray(this.ctx.data).filter(arr => arr.length);
  106 + if (this.historicalData.length) {
105 107 this.activeTrip = this.historicalData[0][0];
106 108 this.calculateIntervals();
107 109 this.timeUpdated(this.minTime);
108   - this.mapWidget.map.map?.invalidateSize();
109   - this.cd.detectChanges();
110 110 }
  111 + this.mapWidget.map.map?.invalidateSize();
  112 + this.cd.detectChanges();
111 113 }
112 114 }
113 115
... ... @@ -117,8 +119,16 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
117 119 }
118 120
119 121 timeUpdated(time: number) {
120   - const currentPosition = this.interpolatedTimeData.map(dataSource => dataSource[time]);
121   - if(isUndefined(currentPosition[0])){
  122 + this.currentTime = time;
  123 + const currentPosition = this.interpolatedTimeData
  124 + .map(dataSource => dataSource[time])
  125 + .filter(ds => ds)
  126 + .map(ds => {
  127 + ds.minTime = this.minTimeFormat;
  128 + ds.maxTime = this.maxTimeFormat;
  129 + return ds;
  130 + });
  131 + if (isUndefined(currentPosition[0])) {
122 132 const timePoints = Object.keys(this.interpolatedTimeData[0]).map(item => parseInt(item, 10));
123 133 for (let i = 1; i < timePoints.length; i++) {
124 134 if (timePoints[i - 1] < time && timePoints[i] > time) {
... ... @@ -134,7 +144,6 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
134 144 }
135 145 }
136 146 }
137   - this.activeTrip = currentPosition[0];
138 147 this.calcLabel();
139 148 this.calcTooltip();
140 149 if (this.mapWidget) {
... ... @@ -145,7 +154,10 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
145 154 if (this.settings.showPoints) {
146 155 this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip);
147 156 }
148   - this.mapWidget.map.updateMarkers(currentPosition);
  157 + this.mapWidget.map.updateMarkers(currentPosition, (trip) => {
  158 + this.activeTrip = trip;
  159 + this.timeUpdated(this.currentTime)
  160 + });
149 161 }
150 162 }
151 163
... ... @@ -185,6 +197,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
185 197 SecurityContext.HTML, tooltipText);
186 198 this.cd.detectChanges();
187 199 }
  200 + this.activeTrip = point;
188 201 return tooltipText;
189 202 }
190 203
... ... @@ -212,7 +225,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
212 225 };
213 226 }
214 227 const timeStamp = Object.keys(result);
215   - for(let i = 0; i < timeStamp.length - 1; i++){
  228 + for (let i = 0; i < timeStamp.length - 1; i++) {
216 229 result[timeStamp[i]].rotationAngle += findAngle(result[timeStamp[i]], result[timeStamp[i + 1]], latKeyName, lngKeyName)
217 230 }
218 231 return result;
... ...
... ... @@ -85,51 +85,53 @@
85 85 formControlName="entityId">
86 86 </tb-entity-select>
87 87 </section>
88   - <div formGroupName="keys">
89   - <mat-expansion-panel formGroupName="attributes" [expanded]="true">
90   - <mat-expansion-panel-header>
91   - <mat-panel-title>
92   - <div class="tb-panel-title" translate>entity-view.attributes-propagation</div>
93   - </mat-panel-title>
94   - </mat-expansion-panel-header>
95   - <div translate class="tb-hint">entity-view.attributes-propagation-hint</div>
96   - <label translate class="tb-title no-padding">entity-view.client-attributes</label>
97   - <tb-entity-keys-list
98   - [entityId]="selectedEntityId | async"
99   - formControlName="cs"
100   - keysText="entity-view.client-attributes-placeholder"
101   - [dataKeyType]="dataKeyType.attribute">
102   - </tb-entity-keys-list>
103   - <label translate class="tb-title no-padding">entity-view.shared-attributes</label>
104   - <tb-entity-keys-list
105   - [entityId]="selectedEntityId | async"
106   - formControlName="sh"
107   - keysText="entity-view.shared-attributes-placeholder"
108   - [dataKeyType]="dataKeyType.attribute">
109   - </tb-entity-keys-list>
110   - <label translate class="tb-title no-padding">entity-view.server-attributes</label>
111   - <tb-entity-keys-list
112   - [entityId]="selectedEntityId | async"
113   - formControlName="ss"
114   - keysText="entity-view.server-attributes-placeholder"
115   - [dataKeyType]="dataKeyType.attribute">
116   - </tb-entity-keys-list>
117   - </mat-expansion-panel>
118   - <mat-expansion-panel [expanded]="true">
119   - <mat-expansion-panel-header>
120   - <mat-panel-title>
121   - <div class="tb-panel-title" translate>entity-view.timeseries-data</div>
122   - </mat-panel-title>
123   - </mat-expansion-panel-header>
124   - <div translate class="tb-hint">entity-view.timeseries-data-hint</div>
125   - <label translate class="tb-title no-padding">entity-view.timeseries</label>
126   - <tb-entity-keys-list
127   - [entityId]="selectedEntityId | async"
128   - formControlName="timeseries"
129   - keysText="entity-view.timeseries-placeholder"
130   - [dataKeyType]="dataKeyType.timeseries">
131   - </tb-entity-keys-list>
132   - </mat-expansion-panel>
  88 + <div class="mat-accordion-container" formGroupName="keys">
  89 + <mat-accordion [multi]="true">
  90 + <mat-expansion-panel formGroupName="attributes" [expanded]="true">
  91 + <mat-expansion-panel-header>
  92 + <mat-panel-title>
  93 + <div class="tb-panel-title" translate>entity-view.attributes-propagation</div>
  94 + </mat-panel-title>
  95 + </mat-expansion-panel-header>
  96 + <div translate class="tb-hint">entity-view.attributes-propagation-hint</div>
  97 + <label translate class="tb-title no-padding">entity-view.client-attributes</label>
  98 + <tb-entity-keys-list
  99 + [entityId]="selectedEntityId | async"
  100 + formControlName="cs"
  101 + keysText="entity-view.client-attributes-placeholder"
  102 + [dataKeyType]="dataKeyType.attribute">
  103 + </tb-entity-keys-list>
  104 + <label translate class="tb-title no-padding">entity-view.shared-attributes</label>
  105 + <tb-entity-keys-list
  106 + [entityId]="selectedEntityId | async"
  107 + formControlName="sh"
  108 + keysText="entity-view.shared-attributes-placeholder"
  109 + [dataKeyType]="dataKeyType.attribute">
  110 + </tb-entity-keys-list>
  111 + <label translate class="tb-title no-padding">entity-view.server-attributes</label>
  112 + <tb-entity-keys-list
  113 + [entityId]="selectedEntityId | async"
  114 + formControlName="ss"
  115 + keysText="entity-view.server-attributes-placeholder"
  116 + [dataKeyType]="dataKeyType.attribute">
  117 + </tb-entity-keys-list>
  118 + </mat-expansion-panel>
  119 + <mat-expansion-panel [expanded]="true">
  120 + <mat-expansion-panel-header>
  121 + <mat-panel-title>
  122 + <div class="tb-panel-title" translate>entity-view.timeseries-data</div>
  123 + </mat-panel-title>
  124 + </mat-expansion-panel-header>
  125 + <div translate class="tb-hint">entity-view.timeseries-data-hint</div>
  126 + <label translate class="tb-title no-padding">entity-view.timeseries</label>
  127 + <tb-entity-keys-list
  128 + [entityId]="selectedEntityId | async"
  129 + formControlName="timeseries"
  130 + keysText="entity-view.timeseries-placeholder"
  131 + [dataKeyType]="dataKeyType.timeseries">
  132 + </tb-entity-keys-list>
  133 + </mat-expansion-panel>
  134 + </mat-accordion>
133 135 </div>
134 136 <tb-datetime
135 137 dateText="entity-view.start-date"
... ...
... ... @@ -14,16 +14,7 @@
14 14 * limitations under the License.
15 15 */
16 16 :host {
17   - mat-expansion-panel {
  17 + .mat-accordion-container {
18 18 margin-bottom: 16px;
19 19 }
20 20 }
21   -
22   -.tb-dialog {
23   - :host {
24   - mat-expansion-panel {
25   - margin-left: 6px;
26   - margin-right: 6px;
27   - }
28   - }
29   -}
... ...
... ... @@ -14,9 +14,15 @@
14 14 * limitations under the License.
15 15 */
16 16 :host ::ng-deep {
17   - .mat-checkbox.hinted-checkbox {
18   - .mat-checkbox-inner-container {
19   - margin-top: 4px;
  17 + .mat-checkbox{
  18 + &.hinted-checkbox {
  19 + .mat-checkbox-inner-container {
  20 + margin-top: 4px;
  21 + }
  22 + }
  23 +
  24 + .mat-checkbox-layout{
  25 + white-space: normal;
20 26 }
21 27 }
22 28 }
... ...
... ... @@ -864,7 +864,6 @@ mat-label {
864 864 min-width: 100%;
865 865 display: flex;
866 866 flex-direction: column;
867   - overflow: auto;
868 867 }
869 868 .mat-dialog-content {
870 869 margin: 0;
... ... @@ -914,7 +913,7 @@ mat-label {
914 913 .mat-dialog-container {
915 914 > *:first-child, form {
916 915 min-width: 100% !important;
917   - height: 100vh;
  916 + height: 100%;
918 917 }
919 918 .mat-dialog-content {
920 919 max-height: 100%;
... ...