This commit is contained in:
ArtemHalushko 2020-05-04 11:40:54 +03:00 committed by GitHub
parent c6cf5c43ad
commit 11db772ea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 73 deletions

View File

@ -17,7 +17,7 @@
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
import { deepClone, isEqual } from '@core/utils';
import { deepClone, isEqual, createLabelFromDatasource } from '@core/utils';
import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service';
import { EntityAliases } from '@shared/models/alias.models';
@ -329,7 +329,7 @@ export class AliasController implements IAliasController {
if (!dataKey.pattern) {
dataKey.pattern = deepClone(dataKey.label);
}
dataKey.label = this.utils.createLabelFromDatasource(datasource, dataKey.pattern);
dataKey.label = createLabelFromDatasource(datasource, dataKey.pattern);
}
getInstantAliasInfo(aliasId: string): AliasInfo {

View File

@ -20,7 +20,7 @@
import { Inject, Injectable, NgZone } from '@angular/core';
import { WINDOW } from '@core/services/window.service';
import { ExceptionData } from '@app/shared/models/error.models';
import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined, createLabelFromDatasource } from '@core/utils';
import { WindowMessage } from '@shared/models/window-message.model';
import { TranslateService } from '@ngx-translate/core';
import { customTranslationsPrefix } from '@app/shared/models/constants';
@ -36,7 +36,7 @@ import { Observable, of, ReplaySubject } from 'rxjs';
const varsRegex = /\$\{([^}]*)\}/g;
const predefinedFunctions: {[func: string]: string} = {
const predefinedFunctions: { [func: string]: string } = {
Sin: 'return Math.round(1000*Math.sin(time/5000));',
Cos: 'return Math.round(1000*Math.cos(time/5000));',
Random: 'var value = prevValue + Math.random() * 100 - 50;\n' +
@ -63,12 +63,12 @@ const defaultAlarmFields: Array<string> = [
alarmFields.status.keyName
];
const commonMaterialIcons: Array<string> = [ 'more_horiz', 'more_vert', 'open_in_new',
const commonMaterialIcons: Array<string> = ['more_horiz', 'more_vert', 'open_in_new',
'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
'share', 'add', 'edit', 'done' ];
'share', 'add', 'edit', 'done'];
// @dynamic
@Injectable({
@ -101,8 +101,8 @@ export class UtilsService {
materialIcons: Array<string> = [];
constructor(@Inject(WINDOW) private window: Window,
private zone: NgZone,
private translate: TranslateService) {
private zone: NgZone,
private translate: TranslateService) {
let frame: Element = null;
try {
frame = window.frameElement;
@ -302,10 +302,10 @@ export class UtilsService {
.split('\n')
.filter((codepoint) => codepoint && codepoint.length);
codepointsArray.forEach((codepoint) => {
const values = codepoint.split(' ');
if (values && values.length === 2) {
this.materialIcons.push(values[0]);
}
const values = codepoint.split(' ');
if (values && values.length === 2) {
this.materialIcons.push(values[0]);
}
});
materialIconsSubject.next(this.materialIcons);
});
@ -360,12 +360,12 @@ export class UtilsService {
}
public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string,
datasources: Datasource[], additionalKeysNumber: number): DataKey {
datasources: Datasource[], additionalKeysNumber: number): DataKey {
const additionalDataKey = deepClone(dataKey);
if (dataKey.settings.comparisonSettings.comparisonValuesLabel) {
additionalDataKey.label = this.createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
} else {
additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.'+timeUnit);
additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.' + timeUnit);
}
additionalDataKey.pattern = additionalDataKey.label;
if (dataKey.settings.comparisonSettings.color) {
@ -380,30 +380,7 @@ export class UtilsService {
}
public createLabelFromDatasource(datasource: Datasource, pattern: string) {
let label = pattern;
if (!datasource) {
return label;
}
let match = varsRegex.exec(pattern);
while (match !== null) {
const variable = match[0];
const variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(datasource.name);
} else if (variableName === 'entityName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'entityLabel') {
label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
}
match = varsRegex.exec(pattern);
}
return label;
return createLabelFromDatasource(datasource, pattern);
}
public generateColors(datasources: Array<Datasource>) {
@ -456,7 +433,7 @@ export class UtilsService {
params = urlQueryString + '&' + newParam;
}
} else if (newParam) {
params = '?' + newParam;
params = '?' + newParam;
}
this.window.history.replaceState({}, '', baseUrl + params);
}

View File

@ -18,6 +18,7 @@ import _ from 'lodash';
import { Observable, Subject, fromEvent, of } from 'rxjs';
import { finalize, share, map } from 'rxjs/operators';
import base64js from 'base64-js';
import { Datasource } from '@app/shared/models/widget.models';
export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
const scrollSubject = new Subject<Event>();
@ -435,6 +436,34 @@ export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
return imageLoad$;
}
export function createLabelFromDatasource(datasource: Datasource, pattern: string) {
const varsRegex = /\$\{([^}]*)\}/g;
let label = pattern;
if (!datasource) {
return label;
}
let match = varsRegex.exec(pattern);
while (match !== null) {
const variable = match[0];
const variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(datasource.name);
} else if (variableName === 'entityName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(datasource.entityName);
} else if (variableName === 'entityLabel') {
label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
}
match = varsRegex.exec(pattern);
}
return label;
}
const imageAspectMap = {};
export function aspectCache(imageUrl: string): Observable<number> {
@ -452,7 +481,6 @@ export function aspectCache(imageUrl: string): Observable<number> {
}
}
export function parseArray(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, dsIndex) =>
@ -523,15 +551,18 @@ export function parseFunction(source: any, params: string[] = ['def']): Function
return res;
}
export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) {
export function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
translateFn?: (key: string) => string) {
let res = '';
try {
if (template.match(/<link-act/g)) {
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>')
.replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
}
if (translateFn) {
template = translateFn(template);
}
template = createLabelFromDatasource(data.$datasource, template);
const formatted = template.match(/\$\{([^}]*)\:\d*\}/g);
if (formatted)
formatted.forEach(value => {

View File

@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { deepClone, isDefined, isNumber } from '@core/utils';
import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@ -282,7 +282,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
alarmsTitle = this.translate.instant('alarm.alarms');
}
this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle);
this.ctx.widgetTitle = createLabelFromDatasource(this.alarmSource, alarmsTitle);
this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true;
if (!this.allowAcknowledgment && !this.allowClear) {

View File

@ -39,7 +39,7 @@ import {
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { deepClone, isDefined, isNumber } from '@core/utils';
import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils';
import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@ -210,7 +210,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
}
const datasource = this.subscription.datasources[0];
this.ctx.widgetTitle = this.utils.createLabelFromDatasource(datasource, entitiesTitle);
this.ctx.widgetTitle = createLabelFromDatasource(datasource, entitiesTitle);
this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true;
this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true;

View File

@ -26,6 +26,7 @@ import { filter } from 'rxjs/operators';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import { DatasourceData } from '@app/shared/models/widget.models';
import { safeExecute } from '@app/core/utils';
export default abstract class LeafletMap {
@ -87,12 +88,14 @@ export default abstract class LeafletMap {
if (this.options.draggableMarker) {
let mousePositionOnMap: L.LatLng;
let addMarker: L.Control;
this.map.on('mouseup', (e: L.LeafletMouseEvent) => {
this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
mousePositionOnMap = e.latlng;
});
const dragListener = (e: L.DragEndEvent) => {
if (e.type === 'dragend' && mousePositionOnMap) {
const newMarker = L.marker(mousePositionOnMap).addTo(this.map);
const icon = new L.Icon.Default();
icon.options.shadowSize = [0, 0];
const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map);
const datasourcesList = document.createElement('div');
const customLatLng = this.convertToCustomFormat(mousePositionOnMap);
this.datasources.forEach(ds => {
@ -195,15 +198,18 @@ export default abstract class LeafletMap {
fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
if (bounds.isValid()) {
if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
if ((!this.options.fitMapBounds || this.options.useDefaultCenterPosition) && this.options.defaultZoomLevel) {
this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
this.map.panTo(bounds.getCenter(), { animate: false });
this.map.panTo(this.options.defaultCenterPosition, { animate: false });
} else {
this.map.once('zoomend', () => {
if (!this.options.defaultZoomLevel && this.map.getZoom() > this.options.minZoomLevel) {
this.map.setZoom(this.options.minZoomLevel, { animate: false });
}
});
if (this.options.useDefaultCenterPosition) {
bounds = bounds.extend(this.options.defaultCenterPosition);
}
this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
}
this.bounds = bounds;
@ -231,8 +237,16 @@ export default abstract class LeafletMap {
updateMarkers(markersData) {
markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ?
safeExecute(this.options.markerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({
html: `<div class="arrow" style="transform: translate(-10px, -10px) rotate(${data.rotationAngle}deg);"><div>`
html: `<div class="arrow"
style="transform: translate(-10px, -10px);
${style}
rotate(${data.rotationAngle}deg);
"><div>`
})
}
else {
@ -335,31 +349,28 @@ export default abstract class LeafletMap {
data.data = JSON.parse(data.data[0][1]) as LatLngTuple[];
}
if (this.polygons.get(data.datasource.entityName)) {
this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options);
this.updatePolygon(data, polyData, this.options);
}
else {
this.createPolygon(data.datasource.entityName, data.data, polyData, this.options);
this.createPolygon(data, polyData, this.options);
}
}
});
}
createPolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
this.ready$.subscribe(() => {
const polygon = new Polygon(this.map, data, dataSources, settings);
const polygon = new Polygon(this.map, polyData, dataSources, settings);
const bounds = this.bounds.extend(polygon.leafletPoly.getBounds());
if (bounds.isValid()) {
this.map.fitBounds(bounds);
this.bounds = bounds;
}
this.polygons.set(key, polygon);
this.fitBounds(bounds);
this.polygons.set(polyData.datasource.entityName, polygon);
});
}
updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
updatePolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
this.ready$.subscribe(() => {
const poly = this.polygons.get(key);
poly.updatePolygon(data, dataSources, settings);
const poly = this.polygons.get(polyData.datasource.entityName);
poly.updatePolygon(polyData.data, dataSources, settings);
this.fitBounds(poly.leafletPoly.getBounds());
});
}

View File

@ -111,9 +111,13 @@ export type PolygonSettings = {
polygonStrokeWeight: number;
polygonStrokeColor: string;
polygonColor: string;
showPolygonTooltip: boolean;
autocloseTooltip: boolean;
tooltipFunction: GenericFunction;
showTooltipAction: string;
tooltipAction: object;
tooltipPattern: string;
useTooltipFunction: boolean;
polygonClick: { [name: string]: actionsHandler };
polygonColorFunction?: GenericFunction;
}

View File

@ -16,8 +16,9 @@
import L, { LatLngExpression, LatLngTuple } from 'leaflet';
import { createTooltip } from './maps-utils';
import { PolygonSettings } from './map-models';
import { PolygonSettings, FormattedData } from './map-models';
import { DatasourceData } from '@app/shared/models/widget.models';
import { safeExecute, parseWithTranslation } from '@app/core/utils';
export class Polygon {
@ -26,8 +27,8 @@ export class Polygon {
data;
dataSources;
constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) {
this.leafletPoly = L.polygon(coordinates, {
constructor(public map, polyData: DatasourceData, dataSources, private settings: PolygonSettings, onClickListener?) {
this.leafletPoly = L.polygon(polyData.data, {
fill: true,
fillColor: settings.polygonColor,
color: settings.polygonStrokeColor,
@ -35,19 +36,29 @@ export class Polygon {
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity
}).addTo(this.map);
if (settings.showTooltip) {
this.dataSources = dataSources;
this.data = polyData;
if (settings.showPolygonTooltip) {
this.tooltip = createTooltip(this.leafletPoly, settings);
this.updateTooltip(polyData);
}
if (onClickListener) {
this.leafletPoly.on('click', onClickListener);
}
}
updateTooltip(data: DatasourceData) {
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));
}
updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
this.data = data;
this.dataSources = dataSources;
this.leafletPoly.setLatLngs(data);
if (settings.showPolygonTooltip)
this.updateTooltip(this.data);
this.updatePolygonColor(settings);
}

View File

@ -477,6 +477,11 @@ export const mapPolygonSchema =
type: 'number',
default: 1
},
showPolygonTooltip: {
title: 'Show polygon tooltip',
type: 'boolean',
default: false
},
usePolygonColorFunction: {
title: 'Use polygon color function',
type: 'boolean',
@ -501,7 +506,7 @@ export const mapPolygonSchema =
key: 'polygonStrokeColor',
type: 'color'
},
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip',
{
key: 'polygonColorFunction',
type: 'javascript'
@ -1137,7 +1142,7 @@ export const tripAnimationSchema = {
rotationAngle: {
title: 'Set additional rotation angle for marker (deg)',
type: 'number',
default: 180
default: 0
},
useMarkerImageFunction: {
title: 'Use marker image function',

View File

@ -24,7 +24,7 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { isDefined, isEqual, isUndefined } from '@core/utils';
import { isDefined, isEqual, isUndefined, createLabelFromDatasource } from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models';
import * as _moment from 'moment';
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
@ -331,7 +331,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
}
public getGroupTitle(datasource: Datasource): string {
return this.utils.createLabelFromDatasource(datasource, this.settings.groupTitle);
return createLabelFromDatasource(datasource, this.settings.groupTitle);
}
public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] {