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

This commit is contained in:
Andrii Shvaika 2020-08-10 12:53:47 +03:00
commit d542b24ad4
16 changed files with 328 additions and 80 deletions

View File

@ -31,7 +31,6 @@ import {
} from '@shared/models/query/query.models';
import { SubscriptionTimewindow } from '@shared/models/time/time.models';
import { AlarmDataListener } from '@core/api/alarm-data.service';
import { UtilsService } from '@core/services/utils.service';
import { PageData } from '@shared/models/page/page-data';
import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils';
import { simulatedAlarm } from '@shared/models/alarm.models';
@ -68,8 +67,7 @@ export class AlarmDataSubscription {
constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions,
private listener: AlarmDataListener,
private telemetryService: TelemetryService,
private utils: UtilsService) {
private telemetryService: TelemetryService) {
}
public unsubscribe() {
@ -166,7 +164,7 @@ export class AlarmDataSubscription {
private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) {
this.pageData = pageData;
this.resetData();
this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities);
this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities);
}
private onDataUpdate(update: Array<AlarmData>) {

View File

@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data';
import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models';
import { Injectable } from '@angular/core';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { UtilsService } from '@core/services/utils.service';
import {
AlarmDataSubscription,
AlarmDataSubscriptionOptions,
@ -31,7 +30,7 @@ import { deepClone } from '@core/utils';
export interface AlarmDataListener {
subscriptionTimewindow?: SubscriptionTimewindow;
alarmSource: Datasource;
alarmsLoaded: (pageData: PageData<AlarmData>, pageLink: AlarmDataPageLink, allowedEntities: number, totalEntities: number) => void;
alarmsLoaded: (pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) => void;
alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void;
subscription?: AlarmDataSubscription;
}
@ -41,8 +40,7 @@ export interface AlarmDataListener {
})
export class AlarmDataService {
constructor(private telemetryService: TelemetryWebsocketService,
private utils: UtilsService) {}
constructor(private telemetryService: TelemetryWebsocketService) {}
public subscribeForAlarms(listener: AlarmDataListener,
@ -88,7 +86,7 @@ export class AlarmDataService {
alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
}
return new AlarmDataSubscription(alarmDataSubscriptionOptions,
listener, this.telemetryService, this.utils);
listener, this.telemetryService);
}
}

View File

@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean {
return typeof value === 'undefined';
}
export function isUndefinedOrNull(value: any): boolean {
return typeof value === 'undefined' || value === null;
}
export function isDefined(value: any): boolean {
return typeof value !== 'undefined';
}
@ -452,7 +456,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin
const variable = match[0];
const variableName = match[1];
if (variableName === name) {
result = result.split(variable).join(value);
result = result.replace(variable, value);
}
match = varsRegex.exec(pattern);
}
@ -469,17 +473,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin
const variable = match[0];
const variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(datasource.name);
label = label.replace(variable, datasource.name);
} else if (variableName === 'entityName') {
label = label.split(variable).join(datasource.entityName);
label = label.replace(variable, datasource.entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(datasource.entityName);
label = label.replace(variable, datasource.entityName);
} else if (variableName === 'entityLabel') {
label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
label = label.replace(variable, datasource.entityLabel || datasource.entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
label = label.replace(variable, datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
label = label.replace(variable, datasource.entityDescription);
}
match = varsRegex.exec(pattern);
}

View File

@ -40,6 +40,7 @@ import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { DomSanitizer } from '@angular/platform-browser';
export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy {
@ -74,6 +75,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
this.ctx.date = $injector.get(DatePipe);
this.ctx.translate = $injector.get(TranslateService);
this.ctx.http = $injector.get(HttpClient);
this.ctx.sanitizer = $injector.get(DomSanitizer);
this.ctx.$scope = this;
if (this.ctx.defaultSubscription) {

View File

@ -121,9 +121,10 @@
</mat-cell>
</ng-container>
<mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row [fxShow]="!alarmsDatasource.dataLoading" [ngClass]="{'mat-row-select': enableSelection,
<mat-row [ngClass]="{'mat-row-select': enableSelection,
'mat-selected': alarmsDatasource.isSelected(alarm),
'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}"
'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm),
'invisible': alarmsDatasource.dataLoading}"
*matRowDef="let alarm; columns: displayedColumns;"
(click)="onRowClick($event, alarm)"></mat-row>
</table>

View File

@ -16,4 +16,23 @@
:host {
width: 100%;
height: 100%;
.tb-table-widget {
.table-container {
position: relative;
}
.mat-table {
.mat-row {
&.invisible {
visibility: hidden;
}
}
}
span.no-data-found {
position: absolute;
top: 60px;
bottom: 0;
left: 0;
right: 0;
}
}
}

View File

@ -82,7 +82,8 @@
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row [fxShow]="!entityDatasource.dataLoading" [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}"
<mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity),
'invisible': entityDatasource.dataLoading}"
*matRowDef="let entity; columns: displayedColumns;"
(click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row>
</table>

View File

@ -16,4 +16,23 @@
:host {
width: 100%;
height: 100%;
.tb-table-widget {
.table-container {
position: relative;
}
.mat-table {
.mat-row {
&.invisible {
visibility: hidden;
}
}
}
span.no-data-found {
position: absolute;
top: 60px;
bottom: 0;
left: 0;
right: 0;
}
}
}

View File

@ -16,6 +16,7 @@
import L, {
FeatureGroup,
Icon,
LatLngBounds,
LatLngTuple,
markerClusterGroup,
@ -32,6 +33,7 @@ import {
MarkerSettings,
PolygonSettings,
PolylineSettings,
ReplaceInfo,
UnitedMapSettings
} from './map-models';
import { Marker } from './markers';
@ -39,7 +41,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import { createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import { WidgetContext } from '@home/models/widget-component.models';
import { DatasourceData } from '@shared/models/widget.models';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
@ -59,6 +61,13 @@ export default abstract class LeafletMap {
points: FeatureGroup;
markersData: FormattedData[] = [];
polygonsData: FormattedData[] = [];
defaultMarkerIconInfo: { size: number[], icon: Icon };
loadingDiv: JQuery<HTMLElement>;
loading = false;
replaceInfoLabelMarker: Array<ReplaceInfo> = [];
markerLabelText: string;
replaceInfoTooltipMarker: Array<ReplaceInfo> = [];
markerTooltipText: string;
protected constructor(public ctx: WidgetContext,
public $container: HTMLElement,
@ -168,6 +177,24 @@ export default abstract class LeafletMap {
}
}
public setLoading(loading: boolean) {
if (this.loading !== loading) {
this.loading = loading;
this.ready$.subscribe(() => {
if (this.loading) {
if (!this.loadingDiv) {
this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading'));
}
this.$container.append(this.loadingDiv[0]);
} else {
if (this.loadingDiv) {
this.loadingDiv.remove();
}
}
});
}
}
public setMap(map: L.Map) {
this.map = map;
if (this.options.useDefaultCenterPosition) {
@ -308,7 +335,11 @@ export default abstract class LeafletMap {
updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata));
this.ready$.subscribe(() => {
const keys: string[] = [];
const toDelete = new Set(Array.from(this.markers.keys()));
const createdMarkers: Marker[] = [];
const updatedMarkers: Marker[] = [];
const deletedMarkers: Marker[] = [];
let m: Marker;
rawMarkers.forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ?
@ -325,22 +356,36 @@ export default abstract class LeafletMap {
this.options.icon = null;
}
if (this.markers.get(data.entityName)) {
this.updateMarker(data.entityName, data, markersData, this.options)
m = this.updateMarker(data.entityName, data, markersData, this.options);
if (m) {
updatedMarkers.push(m);
}
} else {
this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
}
keys.push(data.entityName);
});
const toDelete: string[] = [];
this.markers.forEach((v, mKey) => {
if (!keys.includes(mKey)) {
toDelete.push(mKey);
m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
if (m) {
createdMarkers.push(m);
}
}
toDelete.delete(data.entityName);
});
toDelete.forEach((key) => {
this.deleteMarker(key);
m = this.deleteMarker(key);
if (m) {
deletedMarkers.push(m);
}
});
this.markersData = markersData;
if ((this.options as MarkerSettings).useClusterMarkers) {
if (createdMarkers.length) {
this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker));
}
if (updatedMarkers.length) {
this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker))
}
if (deletedMarkers.length) {
this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker));
}
}
});
}
@ -350,22 +395,20 @@ export default abstract class LeafletMap {
}
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings,
updateBounds = true, callback?) {
const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
updateBounds = true, callback?): Marker {
const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker);
if (callback)
newMarker.leafletMarker.on('click', () => { callback(data, true) });
if (this.bounds && updateBounds)
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
this.markers.set(key, newMarker);
if (this.options.useClusterMarkers) {
this.markersCluster.addLayer(newMarker.leafletMarker);
}
else {
this.map.addLayer(newMarker.leafletMarker);
if (!this.options.useClusterMarkers) {
this.map.addLayer(newMarker.leafletMarker);
}
return newMarker;
}
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker {
const marker: Marker = this.markers.get(key);
const location = this.convertPosition(data)
if (!location.equals(marker.location)) {
@ -374,24 +417,21 @@ export default abstract class LeafletMap {
if (settings.showTooltip) {
marker.updateMarkerTooltip(data);
}
if (settings.useClusterMarkers) {
this.markersCluster.refreshClusters()
}
marker.setDataSources(data, dataSources);
marker.updateMarkerIcon(settings);
return marker;
}
deleteMarker(key: string) {
let marker = this.markers.get(key)?.leafletMarker;
if (marker) {
if (this.options.useClusterMarkers) {
this.markersCluster.removeLayer(marker);
} else {
this.map.removeLayer(marker);
}
this.markers.delete(key);
marker = null;
}
deleteMarker(key: string): Marker {
const marker = this.markers.get(key);
const leafletMarker = marker?.leafletMarker;
if (leafletMarker) {
if (!this.options.useClusterMarkers) {
this.map.removeLayer(leafletMarker);
}
this.markers.delete(key);
}
return marker;
}
updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) {

View File

@ -121,6 +121,12 @@ export interface FormattedData {
[key: string]: any
}
export interface ReplaceInfo {
variable: string;
valDec?: number;
dataKeyName: string
}
export type PolygonSettings = {
showPolygon: boolean;
polygonKeyName: string;

View File

@ -85,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface {
textSearch: null,
dynamic: true
};
this.map.setLoading(true);
this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null);
}
@ -279,6 +280,7 @@ export class MapWidgetController implements MapWidgetInterface {
if (this.settings.draggableMarker) {
this.map.setDataSources(formattedData);
}
this.map.setLoading(false);
}
resize() {

View File

@ -15,13 +15,12 @@
///
import L from 'leaflet';
import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models';
import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models';
import { Datasource, DatasourceData } from '@app/shared/models/widget.models';
import _ from 'lodash';
import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils';
import { Form } from '@angular/forms';
import { createLabelFromDatasource, hashCode, isDefinedAndNotNull, isNumber, isUndefined, padValue } from '@core/utils';
export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings,
@ -185,7 +184,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
} else {
textValue = value;
}
template = template.split(variable).join(textValue);
template = template.replace(variable, textValue);
match = /\${([^}]*)}/g.exec(template);
}
@ -198,7 +197,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.split(actionTags).join(action);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
@ -206,18 +205,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.split(actionTags).join(action);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
const compiled = _.template(template);
res = compiled(data);
// const compiled = _.template(template);
// res = compiled(data);
res = template;
} catch (ex) {
console.log(ex, template)
}
return res;
}
export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
const replaceInfo = [];
try {
const reg = /\${([^}]*)}/g;
let match = reg.exec(template);
while (match !== null) {
const variableInfo: ReplaceInfo = {
dataKeyName: '',
valDec: 2,
variable: ''
};
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
variableInfo.variable = variable;
variableInfo.valDec = valDec;
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
}
} else {
variableInfo.dataKeyName = label;
}
replaceInfo.push(variableInfo);
match = reg.exec(template);
}
} catch (ex) {
console.log(ex, template)
}
return replaceInfo;
}
export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
let text = createLabelFromDatasource(data.$datasource, markerLabelText);
if (replaceInfoLabelMarker) {
for(const variableInfo of replaceInfoLabelMarker) {
let txtVal = '';
if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
const varData = data[variableInfo.dataKeyName];
if (isNumber(varData)) {
txtVal = padValue(varData, variableInfo.valDec);
} else {
txtVal = varData;
}
}
text = text.replace(variableInfo.variable, txtVal);
}
}
return text;
}
function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
if (translateFn) {
template = translateFn(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
let match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
return template;
}
export const parseWithTranslation = {
translateFn: null,
@ -232,6 +320,9 @@ export const parseWithTranslation = {
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
},
prepareProcessPattern(template: string, forceTranslate = false): string {
return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
},
setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn;
}
@ -321,3 +412,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe
}
return pointCoordinate;
}
export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
return $(`
<div style="
z-index: 12;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex-direction: column;
align-content: center;
align-items: center;
justify-content: center;
display: flex;
background: rgba(255,255,255,0.7);
font-size: 16px;
font-family: Roboto;
font-weight: 400;
text-transform: uppercase;
">
<span>${loadingText}</span>
</div>
`);
}

View File

@ -16,9 +16,18 @@
import L, { LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models';
import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import {
aspectCache,
bindPopupActions,
createTooltip,
fillPattern,
parseWithTranslation,
processPattern,
safeExecute
} from './maps-utils';
import tinycolor from 'tinycolor2';
import { isDefined } from '@core/utils';
import LeafletMap from './leaflet-map';
export class Marker {
leafletMarker: L.Marker;
@ -29,7 +38,7 @@ export class Marker {
data: FormattedData;
dataSources: FormattedData[];
constructor(location: L.LatLngExpression, public settings: MarkerSettings,
constructor(private map: LeafletMap, location: L.LatLngExpression, public settings: MarkerSettings,
data?: FormattedData, dataSources?, onDragendListener?) {
this.setDataSources(data, dataSources);
this.leafletMarker = L.marker(location, {
@ -73,9 +82,13 @@ export class Marker {
}
updateMarkerTooltip(data: FormattedData) {
if(!this.map.markerTooltipText || this.settings.useTooltipFunction) {
const pattern = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true);
this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data);
}
this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data));
if (this.tooltip.isOpen() && this.tooltip.getElement()) {
bindPopupActions(this.tooltip, this.settings, data.$datasource);
}
@ -88,9 +101,13 @@ export class Marker {
updateMarkerLabel(settings: MarkerSettings) {
this.leafletMarker.unbindTooltip();
if (settings.showLabel) {
const pattern = settings.useLabelFunction ?
if(!this.map.markerLabelText || settings.useLabelFunction) {
const pattern = settings.useLabelFunction ?
safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label;
settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true);
this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true);
this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data);
}
settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data);
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
}
@ -158,24 +175,24 @@ export class Marker {
}
createDefaultMarkerIcon(color, onMarkerIconReady) {
if (!this.map.defaultMarkerIconInfo) {
const icon = L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
iconSize: [21, 34],
iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
iconSize: [21, 34],
iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
});
const iconInfo = {
size: [21, 34],
icon
this.map.defaultMarkerIconInfo = {
size: [21, 34],
icon
};
onMarkerIconReady(iconInfo);
}
onMarkerIconReady(this.map.defaultMarkerIconInfo);
}
removeMarker() {
/* this.map$.subscribe(map =>
this.leafletMarker.addTo(map))*/

View File

@ -75,6 +75,7 @@ import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { PageLink } from '@shared/models/page/page-link';
import { SortOrder } from '@shared/models/page/sort-order';
import { DomSanitizer } from '@angular/platform-browser';
export interface IWidgetAction {
name: string;
@ -155,6 +156,7 @@ export class WidgetContext {
date: DatePipe;
translate: TranslateService;
http: HttpClient;
sanitizer: DomSanitizer;
private changeDetectorValue: ChangeDetectorRef;

View File

@ -1382,5 +1382,11 @@ export const serviceCompletions: TbEditorCompletions = {
'See <a href="https://angular.io/api/common/http/HttpClient">HttpClient</a> for API reference.',
meta: 'service',
type: '<a href="https://angular.io/api/common/http/HttpClient">HttpClient</a>'
},
sanitizer: {
description: 'DomSanitizer Service<br>' +
'See <a href="https://angular.io/api/platform-browser/DomSanitizer">DomSanitizer</a> for API reference.',
meta: 'service',
type: '<a href="https://angular.io/api/platform-browser/DomSanitizer">DomSanitizer</a>'
}
}

View File

@ -579,6 +579,23 @@ export const widgetContextCompletions: TbEditorCompletions = {
}
]
},
pushAndOpenState: {
description: 'Navigate to new dashboard state and adding intermediate states.',
meta: 'function',
args: [
{
name: 'id',
description: 'An array state object of the target dashboard state.',
type: 'Array <a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/core/api/widget-api.models.ts#L140">StateObject</a>',
},
{
name: 'openRightLayout',
description: 'An optional boolean argument to force open right dashboard layout if present in mobile view mode.',
type: 'boolean',
optional: true
}
]
},
updateState: {
description: 'Updates current dashboard state.',
meta: 'function',