Commit d542b24ad44c527138a3f86228e5f1c5846f5386
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
16 changed files
with
328 additions
and
80 deletions
@@ -31,7 +31,6 @@ import { | @@ -31,7 +31,6 @@ import { | ||
31 | } from '@shared/models/query/query.models'; | 31 | } from '@shared/models/query/query.models'; |
32 | import { SubscriptionTimewindow } from '@shared/models/time/time.models'; | 32 | import { SubscriptionTimewindow } from '@shared/models/time/time.models'; |
33 | import { AlarmDataListener } from '@core/api/alarm-data.service'; | 33 | import { AlarmDataListener } from '@core/api/alarm-data.service'; |
34 | -import { UtilsService } from '@core/services/utils.service'; | ||
35 | import { PageData } from '@shared/models/page/page-data'; | 34 | import { PageData } from '@shared/models/page/page-data'; |
36 | import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils'; | 35 | import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils'; |
37 | import { simulatedAlarm } from '@shared/models/alarm.models'; | 36 | import { simulatedAlarm } from '@shared/models/alarm.models'; |
@@ -68,8 +67,7 @@ export class AlarmDataSubscription { | @@ -68,8 +67,7 @@ export class AlarmDataSubscription { | ||
68 | 67 | ||
69 | constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, | 68 | constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, |
70 | private listener: AlarmDataListener, | 69 | private listener: AlarmDataListener, |
71 | - private telemetryService: TelemetryService, | ||
72 | - private utils: UtilsService) { | 70 | + private telemetryService: TelemetryService) { |
73 | } | 71 | } |
74 | 72 | ||
75 | public unsubscribe() { | 73 | public unsubscribe() { |
@@ -166,7 +164,7 @@ export class AlarmDataSubscription { | @@ -166,7 +164,7 @@ export class AlarmDataSubscription { | ||
166 | private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) { | 164 | private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) { |
167 | this.pageData = pageData; | 165 | this.pageData = pageData; |
168 | this.resetData(); | 166 | this.resetData(); |
169 | - this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities); | 167 | + this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities); |
170 | } | 168 | } |
171 | 169 | ||
172 | private onDataUpdate(update: Array<AlarmData>) { | 170 | private onDataUpdate(update: Array<AlarmData>) { |
@@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data'; | @@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data'; | ||
20 | import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models'; | 20 | import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models'; |
21 | import { Injectable } from '@angular/core'; | 21 | import { Injectable } from '@angular/core'; |
22 | import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; | 22 | import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; |
23 | -import { UtilsService } from '@core/services/utils.service'; | ||
24 | import { | 23 | import { |
25 | AlarmDataSubscription, | 24 | AlarmDataSubscription, |
26 | AlarmDataSubscriptionOptions, | 25 | AlarmDataSubscriptionOptions, |
@@ -31,7 +30,7 @@ import { deepClone } from '@core/utils'; | @@ -31,7 +30,7 @@ import { deepClone } from '@core/utils'; | ||
31 | export interface AlarmDataListener { | 30 | export interface AlarmDataListener { |
32 | subscriptionTimewindow?: SubscriptionTimewindow; | 31 | subscriptionTimewindow?: SubscriptionTimewindow; |
33 | alarmSource: Datasource; | 32 | alarmSource: Datasource; |
34 | - alarmsLoaded: (pageData: PageData<AlarmData>, pageLink: AlarmDataPageLink, allowedEntities: number, totalEntities: number) => void; | 33 | + alarmsLoaded: (pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) => void; |
35 | alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void; | 34 | alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void; |
36 | subscription?: AlarmDataSubscription; | 35 | subscription?: AlarmDataSubscription; |
37 | } | 36 | } |
@@ -41,8 +40,7 @@ export interface AlarmDataListener { | @@ -41,8 +40,7 @@ export interface AlarmDataListener { | ||
41 | }) | 40 | }) |
42 | export class AlarmDataService { | 41 | export class AlarmDataService { |
43 | 42 | ||
44 | - constructor(private telemetryService: TelemetryWebsocketService, | ||
45 | - private utils: UtilsService) {} | 43 | + constructor(private telemetryService: TelemetryWebsocketService) {} |
46 | 44 | ||
47 | 45 | ||
48 | public subscribeForAlarms(listener: AlarmDataListener, | 46 | public subscribeForAlarms(listener: AlarmDataListener, |
@@ -88,7 +86,7 @@ export class AlarmDataService { | @@ -88,7 +86,7 @@ export class AlarmDataService { | ||
88 | alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; | 86 | alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; |
89 | } | 87 | } |
90 | return new AlarmDataSubscription(alarmDataSubscriptionOptions, | 88 | return new AlarmDataSubscription(alarmDataSubscriptionOptions, |
91 | - listener, this.telemetryService, this.utils); | 89 | + listener, this.telemetryService); |
92 | } | 90 | } |
93 | 91 | ||
94 | } | 92 | } |
@@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean { | @@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean { | ||
77 | return typeof value === 'undefined'; | 77 | return typeof value === 'undefined'; |
78 | } | 78 | } |
79 | 79 | ||
80 | +export function isUndefinedOrNull(value: any): boolean { | ||
81 | + return typeof value === 'undefined' || value === null; | ||
82 | +} | ||
83 | + | ||
80 | export function isDefined(value: any): boolean { | 84 | export function isDefined(value: any): boolean { |
81 | return typeof value !== 'undefined'; | 85 | return typeof value !== 'undefined'; |
82 | } | 86 | } |
@@ -452,7 +456,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin | @@ -452,7 +456,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin | ||
452 | const variable = match[0]; | 456 | const variable = match[0]; |
453 | const variableName = match[1]; | 457 | const variableName = match[1]; |
454 | if (variableName === name) { | 458 | if (variableName === name) { |
455 | - result = result.split(variable).join(value); | 459 | + result = result.replace(variable, value); |
456 | } | 460 | } |
457 | match = varsRegex.exec(pattern); | 461 | match = varsRegex.exec(pattern); |
458 | } | 462 | } |
@@ -469,17 +473,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin | @@ -469,17 +473,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin | ||
469 | const variable = match[0]; | 473 | const variable = match[0]; |
470 | const variableName = match[1]; | 474 | const variableName = match[1]; |
471 | if (variableName === 'dsName') { | 475 | if (variableName === 'dsName') { |
472 | - label = label.split(variable).join(datasource.name); | 476 | + label = label.replace(variable, datasource.name); |
473 | } else if (variableName === 'entityName') { | 477 | } else if (variableName === 'entityName') { |
474 | - label = label.split(variable).join(datasource.entityName); | 478 | + label = label.replace(variable, datasource.entityName); |
475 | } else if (variableName === 'deviceName') { | 479 | } else if (variableName === 'deviceName') { |
476 | - label = label.split(variable).join(datasource.entityName); | 480 | + label = label.replace(variable, datasource.entityName); |
477 | } else if (variableName === 'entityLabel') { | 481 | } else if (variableName === 'entityLabel') { |
478 | - label = label.split(variable).join(datasource.entityLabel || datasource.entityName); | 482 | + label = label.replace(variable, datasource.entityLabel || datasource.entityName); |
479 | } else if (variableName === 'aliasName') { | 483 | } else if (variableName === 'aliasName') { |
480 | - label = label.split(variable).join(datasource.aliasName); | 484 | + label = label.replace(variable, datasource.aliasName); |
481 | } else if (variableName === 'entityDescription') { | 485 | } else if (variableName === 'entityDescription') { |
482 | - label = label.split(variable).join(datasource.entityDescription); | 486 | + label = label.replace(variable, datasource.entityDescription); |
483 | } | 487 | } |
484 | match = varsRegex.exec(pattern); | 488 | match = varsRegex.exec(pattern); |
485 | } | 489 | } |
@@ -40,6 +40,7 @@ import { DialogService } from '@core/services/dialog.service'; | @@ -40,6 +40,7 @@ import { DialogService } from '@core/services/dialog.service'; | ||
40 | import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; | 40 | import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; |
41 | import { DatePipe } from '@angular/common'; | 41 | import { DatePipe } from '@angular/common'; |
42 | import { TranslateService } from '@ngx-translate/core'; | 42 | import { TranslateService } from '@ngx-translate/core'; |
43 | +import { DomSanitizer } from '@angular/platform-browser'; | ||
43 | 44 | ||
44 | export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { | 45 | export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { |
45 | 46 | ||
@@ -74,6 +75,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid | @@ -74,6 +75,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid | ||
74 | this.ctx.date = $injector.get(DatePipe); | 75 | this.ctx.date = $injector.get(DatePipe); |
75 | this.ctx.translate = $injector.get(TranslateService); | 76 | this.ctx.translate = $injector.get(TranslateService); |
76 | this.ctx.http = $injector.get(HttpClient); | 77 | this.ctx.http = $injector.get(HttpClient); |
78 | + this.ctx.sanitizer = $injector.get(DomSanitizer); | ||
77 | 79 | ||
78 | this.ctx.$scope = this; | 80 | this.ctx.$scope = this; |
79 | if (this.ctx.defaultSubscription) { | 81 | if (this.ctx.defaultSubscription) { |
@@ -121,9 +121,10 @@ | @@ -121,9 +121,10 @@ | ||
121 | </mat-cell> | 121 | </mat-cell> |
122 | </ng-container> | 122 | </ng-container> |
123 | <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> | 123 | <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> |
124 | - <mat-row [fxShow]="!alarmsDatasource.dataLoading" [ngClass]="{'mat-row-select': enableSelection, | 124 | + <mat-row [ngClass]="{'mat-row-select': enableSelection, |
125 | 'mat-selected': alarmsDatasource.isSelected(alarm), | 125 | 'mat-selected': alarmsDatasource.isSelected(alarm), |
126 | - 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}" | 126 | + 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm), |
127 | + 'invisible': alarmsDatasource.dataLoading}" | ||
127 | *matRowDef="let alarm; columns: displayedColumns;" | 128 | *matRowDef="let alarm; columns: displayedColumns;" |
128 | (click)="onRowClick($event, alarm)"></mat-row> | 129 | (click)="onRowClick($event, alarm)"></mat-row> |
129 | </table> | 130 | </table> |
@@ -16,4 +16,23 @@ | @@ -16,4 +16,23 @@ | ||
16 | :host { | 16 | :host { |
17 | width: 100%; | 17 | width: 100%; |
18 | height: 100%; | 18 | height: 100%; |
19 | + .tb-table-widget { | ||
20 | + .table-container { | ||
21 | + position: relative; | ||
22 | + } | ||
23 | + .mat-table { | ||
24 | + .mat-row { | ||
25 | + &.invisible { | ||
26 | + visibility: hidden; | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + span.no-data-found { | ||
31 | + position: absolute; | ||
32 | + top: 60px; | ||
33 | + bottom: 0; | ||
34 | + left: 0; | ||
35 | + right: 0; | ||
36 | + } | ||
37 | + } | ||
19 | } | 38 | } |
@@ -82,7 +82,8 @@ | @@ -82,7 +82,8 @@ | ||
82 | </mat-cell> | 82 | </mat-cell> |
83 | </ng-container> | 83 | </ng-container> |
84 | <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> | 84 | <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> |
85 | - <mat-row [fxShow]="!entityDatasource.dataLoading" [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}" | 85 | + <mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity), |
86 | + 'invisible': entityDatasource.dataLoading}" | ||
86 | *matRowDef="let entity; columns: displayedColumns;" | 87 | *matRowDef="let entity; columns: displayedColumns;" |
87 | (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row> | 88 | (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row> |
88 | </table> | 89 | </table> |
@@ -16,4 +16,23 @@ | @@ -16,4 +16,23 @@ | ||
16 | :host { | 16 | :host { |
17 | width: 100%; | 17 | width: 100%; |
18 | height: 100%; | 18 | height: 100%; |
19 | + .tb-table-widget { | ||
20 | + .table-container { | ||
21 | + position: relative; | ||
22 | + } | ||
23 | + .mat-table { | ||
24 | + .mat-row { | ||
25 | + &.invisible { | ||
26 | + visibility: hidden; | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + span.no-data-found { | ||
31 | + position: absolute; | ||
32 | + top: 60px; | ||
33 | + bottom: 0; | ||
34 | + left: 0; | ||
35 | + right: 0; | ||
36 | + } | ||
37 | + } | ||
19 | } | 38 | } |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | 16 | ||
17 | import L, { | 17 | import L, { |
18 | FeatureGroup, | 18 | FeatureGroup, |
19 | + Icon, | ||
19 | LatLngBounds, | 20 | LatLngBounds, |
20 | LatLngTuple, | 21 | LatLngTuple, |
21 | markerClusterGroup, | 22 | markerClusterGroup, |
@@ -32,6 +33,7 @@ import { | @@ -32,6 +33,7 @@ import { | ||
32 | MarkerSettings, | 33 | MarkerSettings, |
33 | PolygonSettings, | 34 | PolygonSettings, |
34 | PolylineSettings, | 35 | PolylineSettings, |
36 | + ReplaceInfo, | ||
35 | UnitedMapSettings | 37 | UnitedMapSettings |
36 | } from './map-models'; | 38 | } from './map-models'; |
37 | import { Marker } from './markers'; | 39 | import { Marker } from './markers'; |
@@ -39,7 +41,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; | @@ -39,7 +41,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; | ||
39 | import { filter } from 'rxjs/operators'; | 41 | import { filter } from 'rxjs/operators'; |
40 | import { Polyline } from './polyline'; | 42 | import { Polyline } from './polyline'; |
41 | import { Polygon } from './polygon'; | 43 | import { Polygon } from './polygon'; |
42 | -import { createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; | 44 | +import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; |
43 | import { WidgetContext } from '@home/models/widget-component.models'; | 45 | import { WidgetContext } from '@home/models/widget-component.models'; |
44 | import { DatasourceData } from '@shared/models/widget.models'; | 46 | import { DatasourceData } from '@shared/models/widget.models'; |
45 | import { deepClone, isDefinedAndNotNull } from '@core/utils'; | 47 | import { deepClone, isDefinedAndNotNull } from '@core/utils'; |
@@ -59,6 +61,13 @@ export default abstract class LeafletMap { | @@ -59,6 +61,13 @@ export default abstract class LeafletMap { | ||
59 | points: FeatureGroup; | 61 | points: FeatureGroup; |
60 | markersData: FormattedData[] = []; | 62 | markersData: FormattedData[] = []; |
61 | polygonsData: FormattedData[] = []; | 63 | polygonsData: FormattedData[] = []; |
64 | + defaultMarkerIconInfo: { size: number[], icon: Icon }; | ||
65 | + loadingDiv: JQuery<HTMLElement>; | ||
66 | + loading = false; | ||
67 | + replaceInfoLabelMarker: Array<ReplaceInfo> = []; | ||
68 | + markerLabelText: string; | ||
69 | + replaceInfoTooltipMarker: Array<ReplaceInfo> = []; | ||
70 | + markerTooltipText: string; | ||
62 | 71 | ||
63 | protected constructor(public ctx: WidgetContext, | 72 | protected constructor(public ctx: WidgetContext, |
64 | public $container: HTMLElement, | 73 | public $container: HTMLElement, |
@@ -168,6 +177,24 @@ export default abstract class LeafletMap { | @@ -168,6 +177,24 @@ export default abstract class LeafletMap { | ||
168 | } | 177 | } |
169 | } | 178 | } |
170 | 179 | ||
180 | + public setLoading(loading: boolean) { | ||
181 | + if (this.loading !== loading) { | ||
182 | + this.loading = loading; | ||
183 | + this.ready$.subscribe(() => { | ||
184 | + if (this.loading) { | ||
185 | + if (!this.loadingDiv) { | ||
186 | + this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); | ||
187 | + } | ||
188 | + this.$container.append(this.loadingDiv[0]); | ||
189 | + } else { | ||
190 | + if (this.loadingDiv) { | ||
191 | + this.loadingDiv.remove(); | ||
192 | + } | ||
193 | + } | ||
194 | + }); | ||
195 | + } | ||
196 | + } | ||
197 | + | ||
171 | public setMap(map: L.Map) { | 198 | public setMap(map: L.Map) { |
172 | this.map = map; | 199 | this.map = map; |
173 | if (this.options.useDefaultCenterPosition) { | 200 | if (this.options.useDefaultCenterPosition) { |
@@ -308,7 +335,11 @@ export default abstract class LeafletMap { | @@ -308,7 +335,11 @@ export default abstract class LeafletMap { | ||
308 | updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { | 335 | updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { |
309 | const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); | 336 | const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); |
310 | this.ready$.subscribe(() => { | 337 | this.ready$.subscribe(() => { |
311 | - const keys: string[] = []; | 338 | + const toDelete = new Set(Array.from(this.markers.keys())); |
339 | + const createdMarkers: Marker[] = []; | ||
340 | + const updatedMarkers: Marker[] = []; | ||
341 | + const deletedMarkers: Marker[] = []; | ||
342 | + let m: Marker; | ||
312 | rawMarkers.forEach(data => { | 343 | rawMarkers.forEach(data => { |
313 | if (data.rotationAngle || data.rotationAngle === 0) { | 344 | if (data.rotationAngle || data.rotationAngle === 0) { |
314 | const currentImage = this.options.useMarkerImageFunction ? | 345 | const currentImage = this.options.useMarkerImageFunction ? |
@@ -325,22 +356,36 @@ export default abstract class LeafletMap { | @@ -325,22 +356,36 @@ export default abstract class LeafletMap { | ||
325 | this.options.icon = null; | 356 | this.options.icon = null; |
326 | } | 357 | } |
327 | if (this.markers.get(data.entityName)) { | 358 | if (this.markers.get(data.entityName)) { |
328 | - this.updateMarker(data.entityName, data, markersData, this.options) | 359 | + m = this.updateMarker(data.entityName, data, markersData, this.options); |
360 | + if (m) { | ||
361 | + updatedMarkers.push(m); | ||
362 | + } | ||
329 | } else { | 363 | } else { |
330 | - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); | ||
331 | - } | ||
332 | - keys.push(data.entityName); | ||
333 | - }); | ||
334 | - const toDelete: string[] = []; | ||
335 | - this.markers.forEach((v, mKey) => { | ||
336 | - if (!keys.includes(mKey)) { | ||
337 | - toDelete.push(mKey); | 364 | + m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); |
365 | + if (m) { | ||
366 | + createdMarkers.push(m); | ||
367 | + } | ||
338 | } | 368 | } |
369 | + toDelete.delete(data.entityName); | ||
339 | }); | 370 | }); |
340 | toDelete.forEach((key) => { | 371 | toDelete.forEach((key) => { |
341 | - this.deleteMarker(key); | 372 | + m = this.deleteMarker(key); |
373 | + if (m) { | ||
374 | + deletedMarkers.push(m); | ||
375 | + } | ||
342 | }); | 376 | }); |
343 | this.markersData = markersData; | 377 | this.markersData = markersData; |
378 | + if ((this.options as MarkerSettings).useClusterMarkers) { | ||
379 | + if (createdMarkers.length) { | ||
380 | + this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); | ||
381 | + } | ||
382 | + if (updatedMarkers.length) { | ||
383 | + this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)) | ||
384 | + } | ||
385 | + if (deletedMarkers.length) { | ||
386 | + this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker)); | ||
387 | + } | ||
388 | + } | ||
344 | }); | 389 | }); |
345 | } | 390 | } |
346 | 391 | ||
@@ -350,22 +395,20 @@ export default abstract class LeafletMap { | @@ -350,22 +395,20 @@ export default abstract class LeafletMap { | ||
350 | } | 395 | } |
351 | 396 | ||
352 | private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, | 397 | private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, |
353 | - updateBounds = true, callback?) { | ||
354 | - const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); | 398 | + updateBounds = true, callback?): Marker { |
399 | + const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker); | ||
355 | if (callback) | 400 | if (callback) |
356 | newMarker.leafletMarker.on('click', () => { callback(data, true) }); | 401 | newMarker.leafletMarker.on('click', () => { callback(data, true) }); |
357 | if (this.bounds && updateBounds) | 402 | if (this.bounds && updateBounds) |
358 | this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); | 403 | this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); |
359 | this.markers.set(key, newMarker); | 404 | this.markers.set(key, newMarker); |
360 | - if (this.options.useClusterMarkers) { | ||
361 | - this.markersCluster.addLayer(newMarker.leafletMarker); | ||
362 | - } | ||
363 | - else { | ||
364 | - this.map.addLayer(newMarker.leafletMarker); | 405 | + if (!this.options.useClusterMarkers) { |
406 | + this.map.addLayer(newMarker.leafletMarker); | ||
365 | } | 407 | } |
408 | + return newMarker; | ||
366 | } | 409 | } |
367 | 410 | ||
368 | - private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { | 411 | + private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker { |
369 | const marker: Marker = this.markers.get(key); | 412 | const marker: Marker = this.markers.get(key); |
370 | const location = this.convertPosition(data) | 413 | const location = this.convertPosition(data) |
371 | if (!location.equals(marker.location)) { | 414 | if (!location.equals(marker.location)) { |
@@ -374,24 +417,21 @@ export default abstract class LeafletMap { | @@ -374,24 +417,21 @@ export default abstract class LeafletMap { | ||
374 | if (settings.showTooltip) { | 417 | if (settings.showTooltip) { |
375 | marker.updateMarkerTooltip(data); | 418 | marker.updateMarkerTooltip(data); |
376 | } | 419 | } |
377 | - if (settings.useClusterMarkers) { | ||
378 | - this.markersCluster.refreshClusters() | ||
379 | - } | ||
380 | marker.setDataSources(data, dataSources); | 420 | marker.setDataSources(data, dataSources); |
381 | marker.updateMarkerIcon(settings); | 421 | marker.updateMarkerIcon(settings); |
422 | + return marker; | ||
382 | } | 423 | } |
383 | 424 | ||
384 | - deleteMarker(key: string) { | ||
385 | - let marker = this.markers.get(key)?.leafletMarker; | ||
386 | - if (marker) { | ||
387 | - if (this.options.useClusterMarkers) { | ||
388 | - this.markersCluster.removeLayer(marker); | ||
389 | - } else { | ||
390 | - this.map.removeLayer(marker); | ||
391 | - } | ||
392 | - this.markers.delete(key); | ||
393 | - marker = null; | ||
394 | - } | 425 | + deleteMarker(key: string): Marker { |
426 | + const marker = this.markers.get(key); | ||
427 | + const leafletMarker = marker?.leafletMarker; | ||
428 | + if (leafletMarker) { | ||
429 | + if (!this.options.useClusterMarkers) { | ||
430 | + this.map.removeLayer(leafletMarker); | ||
431 | + } | ||
432 | + this.markers.delete(key); | ||
433 | + } | ||
434 | + return marker; | ||
395 | } | 435 | } |
396 | 436 | ||
397 | updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { | 437 | updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { |
@@ -121,6 +121,12 @@ export interface FormattedData { | @@ -121,6 +121,12 @@ export interface FormattedData { | ||
121 | [key: string]: any | 121 | [key: string]: any |
122 | } | 122 | } |
123 | 123 | ||
124 | +export interface ReplaceInfo { | ||
125 | + variable: string; | ||
126 | + valDec?: number; | ||
127 | + dataKeyName: string | ||
128 | +} | ||
129 | + | ||
124 | export type PolygonSettings = { | 130 | export type PolygonSettings = { |
125 | showPolygon: boolean; | 131 | showPolygon: boolean; |
126 | polygonKeyName: string; | 132 | polygonKeyName: string; |
@@ -85,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -85,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface { | ||
85 | textSearch: null, | 85 | textSearch: null, |
86 | dynamic: true | 86 | dynamic: true |
87 | }; | 87 | }; |
88 | + this.map.setLoading(true); | ||
88 | this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); | 89 | this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); |
89 | } | 90 | } |
90 | 91 | ||
@@ -279,6 +280,7 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -279,6 +280,7 @@ export class MapWidgetController implements MapWidgetInterface { | ||
279 | if (this.settings.draggableMarker) { | 280 | if (this.settings.draggableMarker) { |
280 | this.map.setDataSources(formattedData); | 281 | this.map.setDataSources(formattedData); |
281 | } | 282 | } |
283 | + this.map.setLoading(false); | ||
282 | } | 284 | } |
283 | 285 | ||
284 | resize() { | 286 | resize() { |
@@ -15,13 +15,12 @@ | @@ -15,13 +15,12 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import L from 'leaflet'; | 17 | import L from 'leaflet'; |
18 | -import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models'; | 18 | +import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models'; |
19 | import { Datasource, DatasourceData } from '@app/shared/models/widget.models'; | 19 | import { Datasource, DatasourceData } from '@app/shared/models/widget.models'; |
20 | import _ from 'lodash'; | 20 | import _ from 'lodash'; |
21 | import { Observable, Observer, of } from 'rxjs'; | 21 | import { Observable, Observer, of } from 'rxjs'; |
22 | import { map } from 'rxjs/operators'; | 22 | import { map } from 'rxjs/operators'; |
23 | -import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils'; | ||
24 | -import { Form } from '@angular/forms'; | 23 | +import { createLabelFromDatasource, hashCode, isDefinedAndNotNull, isNumber, isUndefined, padValue } from '@core/utils'; |
25 | 24 | ||
26 | export function createTooltip(target: L.Layer, | 25 | export function createTooltip(target: L.Layer, |
27 | settings: MarkerSettings | PolylineSettings | PolygonSettings, | 26 | settings: MarkerSettings | PolylineSettings | PolygonSettings, |
@@ -185,7 +184,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | @@ -185,7 +184,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | ||
185 | } else { | 184 | } else { |
186 | textValue = value; | 185 | textValue = value; |
187 | } | 186 | } |
188 | - template = template.split(variable).join(textValue); | 187 | + template = template.replace(variable, textValue); |
189 | match = /\${([^}]*)}/g.exec(template); | 188 | match = /\${([^}]*)}/g.exec(template); |
190 | } | 189 | } |
191 | 190 | ||
@@ -198,7 +197,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | @@ -198,7 +197,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | ||
198 | while (match !== null) { | 197 | while (match !== null) { |
199 | [actionTags, actionName, actionText] = match; | 198 | [actionTags, actionName, actionText] = match; |
200 | action = createLinkElement(actionName, actionText); | 199 | action = createLinkElement(actionName, actionText); |
201 | - template = template.split(actionTags).join(action); | 200 | + template = template.replace(actionTags, action); |
202 | match = linkActionRegex.exec(template); | 201 | match = linkActionRegex.exec(template); |
203 | } | 202 | } |
204 | 203 | ||
@@ -206,18 +205,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | @@ -206,18 +205,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: | ||
206 | while (match !== null) { | 205 | while (match !== null) { |
207 | [actionTags, actionName, actionText] = match; | 206 | [actionTags, actionName, actionText] = match; |
208 | action = createButtonElement(actionName, actionText); | 207 | action = createButtonElement(actionName, actionText); |
209 | - template = template.split(actionTags).join(action); | 208 | + template = template.replace(actionTags, action); |
210 | match = buttonActionRegex.exec(template); | 209 | match = buttonActionRegex.exec(template); |
211 | } | 210 | } |
212 | 211 | ||
213 | - const compiled = _.template(template); | ||
214 | - res = compiled(data); | 212 | + // const compiled = _.template(template); |
213 | + // res = compiled(data); | ||
214 | + res = template; | ||
215 | } catch (ex) { | 215 | } catch (ex) { |
216 | console.log(ex, template) | 216 | console.log(ex, template) |
217 | } | 217 | } |
218 | return res; | 218 | return res; |
219 | } | 219 | } |
220 | 220 | ||
221 | +export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> { | ||
222 | + const replaceInfo = []; | ||
223 | + try { | ||
224 | + const reg = /\${([^}]*)}/g; | ||
225 | + let match = reg.exec(template); | ||
226 | + while (match !== null) { | ||
227 | + const variableInfo: ReplaceInfo = { | ||
228 | + dataKeyName: '', | ||
229 | + valDec: 2, | ||
230 | + variable: '' | ||
231 | + }; | ||
232 | + const variable = match[0]; | ||
233 | + let label = match[1]; | ||
234 | + let valDec = 2; | ||
235 | + const splitValues = label.split(':'); | ||
236 | + if (splitValues.length > 1) { | ||
237 | + label = splitValues[0]; | ||
238 | + valDec = parseFloat(splitValues[1]); | ||
239 | + } | ||
240 | + | ||
241 | + variableInfo.variable = variable; | ||
242 | + variableInfo.valDec = valDec; | ||
243 | + | ||
244 | + if (label.startsWith('#')) { | ||
245 | + const keyIndexStr = label.substring(1); | ||
246 | + const n = Math.floor(Number(keyIndexStr)); | ||
247 | + if (String(n) === keyIndexStr && n >= 0) { | ||
248 | + variableInfo.dataKeyName = data.$datasource.dataKeys[n].label; | ||
249 | + } | ||
250 | + } else { | ||
251 | + variableInfo.dataKeyName = label; | ||
252 | + } | ||
253 | + replaceInfo.push(variableInfo); | ||
254 | + | ||
255 | + match = reg.exec(template); | ||
256 | + } | ||
257 | + } catch (ex) { | ||
258 | + console.log(ex, template) | ||
259 | + } | ||
260 | + return replaceInfo; | ||
261 | +} | ||
262 | + | ||
263 | +export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) { | ||
264 | + let text = createLabelFromDatasource(data.$datasource, markerLabelText); | ||
265 | + if (replaceInfoLabelMarker) { | ||
266 | + for(const variableInfo of replaceInfoLabelMarker) { | ||
267 | + let txtVal = ''; | ||
268 | + if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) { | ||
269 | + const varData = data[variableInfo.dataKeyName]; | ||
270 | + if (isNumber(varData)) { | ||
271 | + txtVal = padValue(varData, variableInfo.valDec); | ||
272 | + } else { | ||
273 | + txtVal = varData; | ||
274 | + } | ||
275 | + } | ||
276 | + text = text.replace(variableInfo.variable, txtVal); | ||
277 | + } | ||
278 | + } | ||
279 | + return text; | ||
280 | +} | ||
281 | + | ||
282 | +function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string { | ||
283 | + if (translateFn) { | ||
284 | + template = translateFn(template); | ||
285 | + } | ||
286 | + let actionTags: string; | ||
287 | + let actionText: string; | ||
288 | + let actionName: string; | ||
289 | + let action: string; | ||
290 | + | ||
291 | + let match = linkActionRegex.exec(template); | ||
292 | + while (match !== null) { | ||
293 | + [actionTags, actionName, actionText] = match; | ||
294 | + action = createLinkElement(actionName, actionText); | ||
295 | + template = template.replace(actionTags, action); | ||
296 | + match = linkActionRegex.exec(template); | ||
297 | + } | ||
298 | + | ||
299 | + match = buttonActionRegex.exec(template); | ||
300 | + while (match !== null) { | ||
301 | + [actionTags, actionName, actionText] = match; | ||
302 | + action = createButtonElement(actionName, actionText); | ||
303 | + template = template.replace(actionTags, action); | ||
304 | + match = buttonActionRegex.exec(template); | ||
305 | + } | ||
306 | + return template; | ||
307 | +} | ||
308 | + | ||
221 | export const parseWithTranslation = { | 309 | export const parseWithTranslation = { |
222 | 310 | ||
223 | translateFn: null, | 311 | translateFn: null, |
@@ -232,6 +320,9 @@ export const parseWithTranslation = { | @@ -232,6 +320,9 @@ export const parseWithTranslation = { | ||
232 | parseTemplate(template: string, data: object, forceTranslate = false): string { | 320 | parseTemplate(template: string, data: object, forceTranslate = false): string { |
233 | return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); | 321 | return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); |
234 | }, | 322 | }, |
323 | + prepareProcessPattern(template: string, forceTranslate = false): string { | ||
324 | + return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this)); | ||
325 | + }, | ||
235 | setTranslate(translateFn: TranslateFunc) { | 326 | setTranslate(translateFn: TranslateFunc) { |
236 | this.translateFn = translateFn; | 327 | this.translateFn = translateFn; |
237 | } | 328 | } |
@@ -321,3 +412,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe | @@ -321,3 +412,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe | ||
321 | } | 412 | } |
322 | return pointCoordinate; | 413 | return pointCoordinate; |
323 | } | 414 | } |
415 | + | ||
416 | +export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> { | ||
417 | + return $(` | ||
418 | + <div style=" | ||
419 | + z-index: 12; | ||
420 | + position: absolute; | ||
421 | + top: 0; | ||
422 | + bottom: 0; | ||
423 | + left: 0; | ||
424 | + right: 0; | ||
425 | + flex-direction: column; | ||
426 | + align-content: center; | ||
427 | + align-items: center; | ||
428 | + justify-content: center; | ||
429 | + display: flex; | ||
430 | + background: rgba(255,255,255,0.7); | ||
431 | + font-size: 16px; | ||
432 | + font-family: Roboto; | ||
433 | + font-weight: 400; | ||
434 | + text-transform: uppercase; | ||
435 | + "> | ||
436 | + <span>${loadingText}</span> | ||
437 | + </div> | ||
438 | + `); | ||
439 | +} |
@@ -16,9 +16,18 @@ | @@ -16,9 +16,18 @@ | ||
16 | 16 | ||
17 | import L, { LeafletMouseEvent } from 'leaflet'; | 17 | import L, { LeafletMouseEvent } from 'leaflet'; |
18 | import { FormattedData, MarkerSettings } from './map-models'; | 18 | import { FormattedData, MarkerSettings } from './map-models'; |
19 | -import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; | 19 | +import { |
20 | + aspectCache, | ||
21 | + bindPopupActions, | ||
22 | + createTooltip, | ||
23 | + fillPattern, | ||
24 | + parseWithTranslation, | ||
25 | + processPattern, | ||
26 | + safeExecute | ||
27 | +} from './maps-utils'; | ||
20 | import tinycolor from 'tinycolor2'; | 28 | import tinycolor from 'tinycolor2'; |
21 | import { isDefined } from '@core/utils'; | 29 | import { isDefined } from '@core/utils'; |
30 | +import LeafletMap from './leaflet-map'; | ||
22 | 31 | ||
23 | export class Marker { | 32 | export class Marker { |
24 | leafletMarker: L.Marker; | 33 | leafletMarker: L.Marker; |
@@ -29,7 +38,7 @@ export class Marker { | @@ -29,7 +38,7 @@ export class Marker { | ||
29 | data: FormattedData; | 38 | data: FormattedData; |
30 | dataSources: FormattedData[]; | 39 | dataSources: FormattedData[]; |
31 | 40 | ||
32 | - constructor(location: L.LatLngExpression, public settings: MarkerSettings, | 41 | + constructor(private map: LeafletMap, location: L.LatLngExpression, public settings: MarkerSettings, |
33 | data?: FormattedData, dataSources?, onDragendListener?) { | 42 | data?: FormattedData, dataSources?, onDragendListener?) { |
34 | this.setDataSources(data, dataSources); | 43 | this.setDataSources(data, dataSources); |
35 | this.leafletMarker = L.marker(location, { | 44 | this.leafletMarker = L.marker(location, { |
@@ -73,9 +82,13 @@ export class Marker { | @@ -73,9 +82,13 @@ export class Marker { | ||
73 | } | 82 | } |
74 | 83 | ||
75 | updateMarkerTooltip(data: FormattedData) { | 84 | updateMarkerTooltip(data: FormattedData) { |
85 | + if(!this.map.markerTooltipText || this.settings.useTooltipFunction) { | ||
76 | const pattern = this.settings.useTooltipFunction ? | 86 | const pattern = this.settings.useTooltipFunction ? |
77 | - safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; | ||
78 | - this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); | 87 | + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; |
88 | + this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true); | ||
89 | + this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data); | ||
90 | + } | ||
91 | + this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data)); | ||
79 | if (this.tooltip.isOpen() && this.tooltip.getElement()) { | 92 | if (this.tooltip.isOpen() && this.tooltip.getElement()) { |
80 | bindPopupActions(this.tooltip, this.settings, data.$datasource); | 93 | bindPopupActions(this.tooltip, this.settings, data.$datasource); |
81 | } | 94 | } |
@@ -88,9 +101,13 @@ export class Marker { | @@ -88,9 +101,13 @@ export class Marker { | ||
88 | updateMarkerLabel(settings: MarkerSettings) { | 101 | updateMarkerLabel(settings: MarkerSettings) { |
89 | this.leafletMarker.unbindTooltip(); | 102 | this.leafletMarker.unbindTooltip(); |
90 | if (settings.showLabel) { | 103 | if (settings.showLabel) { |
91 | - const pattern = settings.useLabelFunction ? | 104 | + if(!this.map.markerLabelText || settings.useLabelFunction) { |
105 | + const pattern = settings.useLabelFunction ? | ||
92 | safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; | 106 | safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; |
93 | - settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true); | 107 | + this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); |
108 | + this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data); | ||
109 | + } | ||
110 | + settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data); | ||
94 | this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`, | 111 | this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`, |
95 | { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); | 112 | { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); |
96 | } | 113 | } |
@@ -158,24 +175,24 @@ export class Marker { | @@ -158,24 +175,24 @@ export class Marker { | ||
158 | } | 175 | } |
159 | 176 | ||
160 | createDefaultMarkerIcon(color, onMarkerIconReady) { | 177 | createDefaultMarkerIcon(color, onMarkerIconReady) { |
178 | + if (!this.map.defaultMarkerIconInfo) { | ||
161 | const icon = L.icon({ | 179 | const icon = L.icon({ |
162 | - iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, | ||
163 | - iconSize: [21, 34], | ||
164 | - iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], | ||
165 | - popupAnchor: [0, -34], | ||
166 | - shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', | ||
167 | - shadowSize: [40, 37], | ||
168 | - shadowAnchor: [12, 35] | 180 | + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, |
181 | + iconSize: [21, 34], | ||
182 | + iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], | ||
183 | + popupAnchor: [0, -34], | ||
184 | + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', | ||
185 | + shadowSize: [40, 37], | ||
186 | + shadowAnchor: [12, 35] | ||
169 | }); | 187 | }); |
170 | - const iconInfo = { | ||
171 | - size: [21, 34], | ||
172 | - icon | 188 | + this.map.defaultMarkerIconInfo = { |
189 | + size: [21, 34], | ||
190 | + icon | ||
173 | }; | 191 | }; |
174 | - onMarkerIconReady(iconInfo); | 192 | + } |
193 | + onMarkerIconReady(this.map.defaultMarkerIconInfo); | ||
175 | } | 194 | } |
176 | 195 | ||
177 | - | ||
178 | - | ||
179 | removeMarker() { | 196 | removeMarker() { |
180 | /* this.map$.subscribe(map => | 197 | /* this.map$.subscribe(map => |
181 | this.leafletMarker.addTo(map))*/ | 198 | this.leafletMarker.addTo(map))*/ |
@@ -75,6 +75,7 @@ import { DatePipe } from '@angular/common'; | @@ -75,6 +75,7 @@ import { DatePipe } from '@angular/common'; | ||
75 | import { TranslateService } from '@ngx-translate/core'; | 75 | import { TranslateService } from '@ngx-translate/core'; |
76 | import { PageLink } from '@shared/models/page/page-link'; | 76 | import { PageLink } from '@shared/models/page/page-link'; |
77 | import { SortOrder } from '@shared/models/page/sort-order'; | 77 | import { SortOrder } from '@shared/models/page/sort-order'; |
78 | +import { DomSanitizer } from '@angular/platform-browser'; | ||
78 | 79 | ||
79 | export interface IWidgetAction { | 80 | export interface IWidgetAction { |
80 | name: string; | 81 | name: string; |
@@ -155,6 +156,7 @@ export class WidgetContext { | @@ -155,6 +156,7 @@ export class WidgetContext { | ||
155 | date: DatePipe; | 156 | date: DatePipe; |
156 | translate: TranslateService; | 157 | translate: TranslateService; |
157 | http: HttpClient; | 158 | http: HttpClient; |
159 | + sanitizer: DomSanitizer; | ||
158 | 160 | ||
159 | private changeDetectorValue: ChangeDetectorRef; | 161 | private changeDetectorValue: ChangeDetectorRef; |
160 | 162 |
@@ -1382,5 +1382,11 @@ export const serviceCompletions: TbEditorCompletions = { | @@ -1382,5 +1382,11 @@ export const serviceCompletions: TbEditorCompletions = { | ||
1382 | 'See <a href="https://angular.io/api/common/http/HttpClient">HttpClient</a> for API reference.', | 1382 | 'See <a href="https://angular.io/api/common/http/HttpClient">HttpClient</a> for API reference.', |
1383 | meta: 'service', | 1383 | meta: 'service', |
1384 | type: '<a href="https://angular.io/api/common/http/HttpClient">HttpClient</a>' | 1384 | type: '<a href="https://angular.io/api/common/http/HttpClient">HttpClient</a>' |
1385 | + }, | ||
1386 | + sanitizer: { | ||
1387 | + description: 'DomSanitizer Service<br>' + | ||
1388 | + 'See <a href="https://angular.io/api/platform-browser/DomSanitizer">DomSanitizer</a> for API reference.', | ||
1389 | + meta: 'service', | ||
1390 | + type: '<a href="https://angular.io/api/platform-browser/DomSanitizer">DomSanitizer</a>' | ||
1385 | } | 1391 | } |
1386 | } | 1392 | } |
@@ -579,6 +579,23 @@ export const widgetContextCompletions: TbEditorCompletions = { | @@ -579,6 +579,23 @@ export const widgetContextCompletions: TbEditorCompletions = { | ||
579 | } | 579 | } |
580 | ] | 580 | ] |
581 | }, | 581 | }, |
582 | + pushAndOpenState: { | ||
583 | + description: 'Navigate to new dashboard state and adding intermediate states.', | ||
584 | + meta: 'function', | ||
585 | + args: [ | ||
586 | + { | ||
587 | + name: 'id', | ||
588 | + description: 'An array state object of the target dashboard state.', | ||
589 | + type: 'Array <a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/core/api/widget-api.models.ts#L140">StateObject</a>', | ||
590 | + }, | ||
591 | + { | ||
592 | + name: 'openRightLayout', | ||
593 | + description: 'An optional boolean argument to force open right dashboard layout if present in mobile view mode.', | ||
594 | + type: 'boolean', | ||
595 | + optional: true | ||
596 | + } | ||
597 | + ] | ||
598 | + }, | ||
582 | updateState: { | 599 | updateState: { |
583 | description: 'Updates current dashboard state.', | 600 | description: 'Updates current dashboard state.', |
584 | meta: 'function', | 601 | meta: 'function', |