Merge branch 'master' of github.com:thingsboard/thingsboard
This commit is contained in:
commit
d542b24ad4
@ -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>) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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>
|
||||
`);
|
||||
}
|
||||
|
||||
@ -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))*/
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>'
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user