Commit dcd00e587a53bbef34000ff0e3b304c03f469adb
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
10 changed files
with
127 additions
and
97 deletions
... | ... | @@ -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%; | ... | ... |