map fixes

This commit is contained in:
Adsumus 2020-04-24 11:22:26 +03:00
parent 8460358f06
commit 8979a511a3
20 changed files with 461 additions and 337 deletions

View File

@ -39,7 +39,9 @@
"node_modules/rc-select/assets/index.less",
"node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
"node_modules/leaflet/dist/leaflet.css",
"src/app/modules/home/components/widget/lib/maps/markers.scss"
"src/app/modules/home/components/widget/lib/maps/markers.scss",
"node_modules/leaflet.markercluster/dist/MarkerCluster.css",
"node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"
],
"stylePreprocessorOptions": {
"includePaths": [

View File

@ -1811,8 +1811,7 @@
"@types/geojson": {
"version": "7946.0.7",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",
"dev": true
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ=="
},
"@types/glob": {
"version": "7.1.1",
@ -1876,7 +1875,6 @@
"version": "1.5.12",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.12.tgz",
"integrity": "sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==",
"dev": true,
"requires": {
"@types/geojson": "*"
}
@ -1890,6 +1888,14 @@
"@types/leaflet": "*"
}
},
"@types/leaflet.markercluster": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.4.2.tgz",
"integrity": "sha512-QQ//hevAxMH2dlRQdRre7V/1G+TbtuDtZnZF/75TNwVIgklrsQVCIcS/cvLsl7UUryfPJ6xmoYHfFzK5iGVgpg==",
"requires": {
"@types/leaflet": "*"
}
},
"@types/lodash": {
"version": "4.14.150",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz",
@ -8605,7 +8611,7 @@
"integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
},
"ngx-flowchart": {
"version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f",
"version": "git://github.com/thingsboard/ngx-flowchart.git#97a77477ca8579becf0e3a07866046b4536fe30a",
"from": "git://github.com/thingsboard/ngx-flowchart.git#master",
"requires": {
"tslib": "^1.10.0"

View File

@ -108,6 +108,7 @@
"@types/jstree": "^3.3.39",
"@types/jszip": "^3.1.7",
"@types/leaflet": "^1.5.12",
"@types/leaflet.markercluster": "^1.4.2",
"@types/leaflet-polylinedecorator": "^1.6.0",
"@types/lodash": "^4.14.150",
"@types/raphael": "^2.3.0",

View File

@ -15,7 +15,7 @@
///
import _ from 'lodash';
import { Observable, Subject, from, fromEvent, of } from 'rxjs';
import { Observable, Subject, fromEvent, of } from 'rxjs';
import { finalize, share, map } from 'rxjs/operators';
import base64js from 'base64-js';
@ -430,7 +430,7 @@ export function getDescendantProp(obj: any, path: string): any {
export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
const image = new Image();
const imageLoad$ = fromEvent(image, 'load').pipe(map(event => image));
const imageLoad$ = fromEvent(image, 'load').pipe(map(() => image));
image.src = imageUrl;
return imageLoad$;
}
@ -524,37 +524,49 @@ export function parseFunction(source: any, params: string[] = []): Function {
return res;
}
export function parseTemplate(template: string, data: object) {
export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) {
let res = '';
let variables = '';
try {
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
let variables = '';
const expressions = template
.match(/\{(.*?)\}/g) // find expressions
.map(exp => exp.replace(/{|}/g, '')) // remove brackets
.map(exp => exp.split(':'))
.map(arr => {
variables += `let ${arr[0]} = ''; `;
return arr;
})
.filter(arr => !!arr[1]) // filter expressions without format
.reduce((res, current) => {
res[current[0]] = current[1];
return res;
}, {});
for (const key in data) {
if (!key.includes('|'))
variables += `${key} = '${expressions[key] ? padValue(data[key], +expressions[key]) : data[key]}';`;
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(/:\d+}/g, '}');
res = safeExecute(parseFunction(variables + 'return' + '`' + template + '`'));
if (template.includes('i18n')) {
const translateRegexp = /\{i18n:(.*?)\}/;
template.match(new RegExp(translateRegexp.source, translateRegexp.flags + 'g')).forEach(match => {
template = template.replace(match, translateFn(match.match(translateRegexp)[1]));
});
}
const expressions = template.match(/\{(.*?)\}/g);
if (expressions) {
const clearMatches = template.match(/(?<=\{)(.+?)(?=(\}|\:))/g);
for (const key in data) {
if (!key.includes('|'))
variables += `let ${key} = '${clearMatches[key] ? padValue(data[key], +clearMatches[key]) : data[key]}';`;
}
template = template.replace(/\:\d+\}/g, '}');
res = safeExecute(parseFunction(variables + ' return' + '`' + template + '`'));
}
else res = template;
}
catch (ex) {
console.log(ex, variables, template)
}
return res;
}
export let parseWithTranslation = {
translate(): string {
throw console.error('Translate not assigned');
},
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this?.translate);
},
setTranslate(translateFn: (key: string, defaultTranslation?: string) => string) {
this.translate = translateFn;
}
}
export function padValue(val: any, dec: number): string {
let strVal;
let n;

View File

@ -14,12 +14,12 @@
/// limitations under the License.
///
import L, { LatLngTuple } from 'leaflet';
import L, { LatLngTuple, LatLngBounds, Point } from 'leaflet';
import 'leaflet-providers';
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
import 'leaflet.markercluster/dist/leaflet.markercluster'
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import LM from 'leaflet.markercluster/dist/leaflet.markercluster';
import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models';
import { Marker } from './markers';
@ -34,7 +34,7 @@ export default abstract class LeafletMap {
markers: Map<string, Marker> = new Map();
polylines: Map<string, Polyline> = new Map();
polygons: Map<string, Polygon> = new Map();
dragMode = true;
dragMode = false;
map: L.Map;
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
@ -43,6 +43,7 @@ export default abstract class LeafletMap {
bounds: L.LatLngBounds;
newMarker: L.Marker;
datasources: FormattedData[];
markersCluster: LM.markerClusterGroup;
constructor(public $container: HTMLElement, options: UnitedMapSettings) {
this.options = options;
@ -50,13 +51,38 @@ export default abstract class LeafletMap {
public initSettings(options: MapSettings) {
const { initCallback,
disableScrollZooming, }: MapSettings = options;
disableScrollZooming,
useClusterMarkers,
zoomOnClick,
showCoverageOnHover,
removeOutsideVisibleBounds,
animate,
chunkedLoading,
maxClusterRadius,
maxZoom }: MapSettings = options;
if (disableScrollZooming) {
this.map.scrollWheelZoom.disable();
}
if (initCallback) {
setTimeout(options.initCallback, 0);
}
if (useClusterMarkers) {
const clusteringSettings: LM.MarkerClusterGroupOptions = {
zoomToBoundsOnClick: zoomOnClick,
showCoverageOnHover,
removeOutsideVisibleBounds,
animate,
chunkedLoading
};
if (maxClusterRadius && maxClusterRadius > 0) {
clusteringSettings.maxClusterRadius = Math.floor(maxClusterRadius);
}
if (maxZoom && maxZoom >= 0 && maxZoom < 19) {
clusteringSettings.disableClusteringAtZoom = Math.floor(maxZoom);
}
this.markersCluster = LM.markerClusterGroup(clusteringSettings);
this.ready$.subscribe(map => map.addLayer(this.markersCluster));
}
}
addMarkerControl() {
@ -80,7 +106,7 @@ export default abstract class LeafletMap {
this.saveMarkerLocation(updatedEnttity);
this.map.removeLayer(newMarker);
this.deleteMarker(ds.entityName);
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options, false);
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options);
}
datasourcesList.append(dsItem);
});
@ -103,7 +129,7 @@ export default abstract class LeafletMap {
img.src = `assets/add_location.svg`;
img.style.width = '32px';
img.style.height = '32px';
img.title = "Drag and drop to add marker";
img.title = 'Drag and drop to add marker';
img.onclick = this.dragMarker;
img.draggable = true;
const draggableImg = new L.Draggable(img);
@ -115,11 +141,9 @@ export default abstract class LeafletMap {
},
dragMarker: this.dragMarker
} as any);
L.control.addMarker = (opts) => {
return new L.Control.AddMarker(opts);
}
addMarker = L.control.addMarker({ position: 'topright' }).addTo(this.map);
}
}
@ -171,9 +195,9 @@ export default abstract class LeafletMap {
return this.map.getCenter();
}
fitBounds(bounds, useDefaultZoom = false) {
fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
if (bounds.isValid()) {
if ((this.options.dontFitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
this.map.panTo(bounds.getCenter(), { animate: false });
} else {
@ -182,7 +206,7 @@ export default abstract class LeafletMap {
this.map.setZoom(this.options.minZoomLevel, { animate: false });
}
});
this.map.fitBounds(bounds, { padding: [50, 50], animate: false });
this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
}
this.bounds = this.bounds.extend(bounds);
}
@ -232,11 +256,17 @@ export default abstract class LeafletMap {
this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
}
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, setFocus = true) {
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
this.ready$.subscribe(() => {
const newMarker = new Marker(this.map, this.convertPosition(data), settings, data, dataSources, () => { }, this.dragMarker);
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), setFocus);
const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size > 2);
this.markers.set(key, newMarker);
if (this.options.useClusterMarkers) {
this.markersCluster.addLayer(newMarker.leafletMarker);
}
else {
this.map.addLayer(newMarker.leafletMarker);
}
});
}
@ -249,6 +279,8 @@ export default abstract class LeafletMap {
if (settings.showTooltip) {
marker.updateMarkerTooltip(data);
}
if (settings.useClusterMarkers)
this.markersCluster.refreshClusters()
marker.setDataSources(data, dataSources);
marker.updateMarkerIcon(settings);
}
@ -327,7 +359,9 @@ export default abstract class LeafletMap {
updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
this.ready$.subscribe(() => {
this.polygons.get(key).updatePolygon(data, dataSources, settings);
const poly = this.polygons.get(key);
poly.updatePolygon(data, dataSources, settings);
this.fitBounds(poly.leafletPoly.getBounds());
});
}
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { LatLngTuple } from 'leaflet';
import { LatLngTuple, LeafletMouseEvent } from 'leaflet';
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
@ -24,14 +24,15 @@ export type MapSettings = {
draggableMarker: boolean;
initCallback?: () => any;
defaultZoomLevel?: number;
dontFitMapBounds?: boolean;
disableScrollZooming?: boolean;
minZoomLevel?: number;
useClusterMarkers: boolean;
latKeyName?: string;
lngKeyName?: string;
xPosKeyName?: string;
yPosKeyName?: string;
mapProvider: MapProviders;
mapProviderHere: string;
mapUrl?: string;
mapImageUrl?: string;
provider?: MapProviders;
@ -42,6 +43,13 @@ export type MapSettings = {
gmDefaultMapType?: string;
useLabelFunction: string;
icon?: any;
zoomOnClick: boolean,
maxZoom: number,
showCoverageOnHover: boolean,
animate: boolean,
maxClusterRadius: number,
chunkedLoading: boolean,
removeOutsideVisibleBounds: boolean
}
export enum MapProviders {
@ -54,7 +62,7 @@ export enum MapProviders {
export type MarkerSettings = {
tooltipPattern?: any;
tooltipActions: object;
tooltipAction: { [name: string]: actionsHandler };
icon?: any;
showLabel?: boolean;
label: string;
@ -63,19 +71,19 @@ export type MarkerSettings = {
useLabelFunction: boolean;
draggableMarker: boolean;
showTooltip?: boolean;
useTooltipFunction: boolean;
useColorFunction: boolean;
color?: string;
autocloseTooltip: boolean;
displayTooltipAction: string;
showTooltipAction: string;
useClusterMarkers: boolean;
currentImage?: string;
useMarkerImageFunction?: boolean;
markerImages?: string[];
useMarkerImage: boolean;
markerImageSize: number;
fitMapBounds: boolean;
markerImage: {
length: number
}
markerImage: string;
markerClick: { [name: string]: actionsHandler };
colorFunction: GenericFunction;
tooltipFunction: GenericFunction;
labelFunction: GenericFunction;
@ -99,18 +107,18 @@ export type PolygonSettings = {
polygonStrokeColor: string;
polygonColor: string;
autocloseTooltip: boolean;
displayTooltipAction: string;
tooltipActions: object;
showTooltipAction: string;
tooltipAction: object;
polygonClick: { [name: string]: actionsHandler };
polygonColorFunction?: GenericFunction;
}
export type PolylineSettings = {
usePolylineDecorator: any;
autocloseTooltip: boolean;
displayTooltipAction: string;
showTooltipAction: string;
useColorFunction: any;
tooltipActions: object;
tooltipAction: { [name: string]: actionsHandler };
color: string;
useStrokeOpacityFunction: any;
strokeOpacity: number;
@ -134,4 +142,6 @@ export interface HistorySelectSettings {
buttonColor: string;
}
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
export type actionsHandler = ($event: Event | LeafletMouseEvent) => void;
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;

View File

@ -15,6 +15,7 @@
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
import { MapProviders } from './map-models';
export interface MapWidgetInterface {
resize(),
@ -24,8 +25,8 @@ export interface MapWidgetInterface {
}
export interface MapWidgetStaticInterface {
settingsSchema(mapProvider?, drawRoutes?): JsonSettingsSchema;
getProvidersSchema():JsonSettingsSchema
settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema;
getProvidersSchema(mapProvider?: MapProviders): JsonSettingsSchema
dataKeySettingsSchema(): object;
actionSources(): object;
}

View File

@ -26,11 +26,12 @@ import {
markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet,
hereMapSettingsSchema,
mapProviderSchema
mapProviderSchema,
mapPolygonSchema
} from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
import { parseFunction, parseArray, parseData, safeExecute } from '@core/utils';
import { parseFunction, parseArray, parseData, safeExecute, parseWithTranslation } from '@core/utils';
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
import { forkJoin } from 'rxjs';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
@ -40,12 +41,13 @@ import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service';
import { Type } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@app/core/public-api';
// @dynamic
export class MapWidgetController implements MapWidgetInterface {
constructor(public mapProvider: MapProviders, private drawRoutes: boolean, public ctx: WidgetContext, $element: HTMLElement) {
console.log("MapWidgetController -> constructor -> ctx", ctx)
if (this.map) {
this.map.map.remove();
delete this.map;
@ -56,15 +58,16 @@ export class MapWidgetController implements MapWidgetInterface {
$element = ctx.$container[0];
}
this.settings = this.initSettings(ctx.settings);
const descriptors = this.ctx.actionsApi.getActionDescriptors('tooltipAction');
this.settings.tooltipActions = {};
descriptors.forEach(descriptor => {
this.settings.tooltipActions[descriptor.name] = ($event) => this.onTooltipClick(descriptor, $event);
}, this.settings.tooltipActions);
this.settings.tooltipAction = this.getDescriptors('tooltipAction');
this.settings.markerClick = this.getDescriptors('markerClick');
this.settings.polygonClick = this.getDescriptors('polygonClick');
// this.settings.
const MapClass = providerSets[this.provider]?.MapClass;
if (!MapClass) {
return;
}
parseWithTranslation.setTranslate(this.translate);
this.map = new MapClass($element, this.settings);
this.map.saveMarkerLocation = this.setMarkerLocation;
}
@ -74,13 +77,13 @@ export class MapWidgetController implements MapWidgetInterface {
schema: JsonSettingsSchema;
data;
settings: UnitedMapSettings;
actions: Map<string, Map<string, (widgetContext: WidgetContext) => void>>;
public static dataKeySettingsSchema(): object {
return {};
}
public static getProvidersSchema() {
public static getProvidersSchema(mapProvider: MapProviders) {
mapProviderSchema.schema.properties.provider.default = mapProvider;
return mergeSchemes([mapProviderSchema,
...Object.values(providerSets)?.map(
(setting: IProvider) => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]);
@ -88,18 +91,20 @@ export class MapWidgetController implements MapWidgetInterface {
public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
const schema = initSchema();
addToSchema(schema, this.getProvidersSchema());
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, commonMapSettingsSchema);
addToSchema(schema, this.getProvidersSchema(mapProvider));
if(mapProvider!=='image-map'){
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, mergeSchemes([commonMapSettingsSchema, addCondition(mapPolygonSchema, 'model.showPolygon === true')]));
addGroupInfo(schema, 'Common Map Settings');
if (drawRoutes) {
addToSchema(schema, routeMapSettingsSchema);
addGroupInfo(schema, 'Route Map Settings');
} else if (mapProvider !== 'image-map') {
const clusteringSchema = mergeSchemes([markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchema])
const clusteringSchema = mergeSchemes([markerClusteringSettingsSchema,
addCondition(markerClusteringSettingsSchemaLeaflet, `model.useClusterMarkers === true`)])
addToSchema(schema, clusteringSchema);
addGroupInfo(schema, 'Markers Clustering Settings');
}
}}
return schema;
}
@ -120,14 +125,28 @@ export class MapWidgetController implements MapWidgetInterface {
};
}
translate = (key: string, defaultTranslation?: string):string => {
return (this.ctx.$injector.get(UtilsService).customTranslation(key, defaultTranslation || key)
|| this.ctx.$injector.get(TranslateService).instant(key));
}
getDescriptors(name: string): { [name: string]: ($event: Event) => void } {
const descriptors = this.ctx.actionsApi.getActionDescriptors(name);
const actions = {};
descriptors.forEach(descriptor => {
actions[descriptor.name] = ($event: Event) => this.onCustomAction(descriptor, $event);
}, actions);
return actions;
}
onInit() {
}
private onTooltipClick(descriptor: WidgetActionDescriptor, $event: any) {
if ($event) {
$event.stopPropagation();
private onCustomAction(descriptor: WidgetActionDescriptor, $event: any) {
if ($event & $event.stopPropagation) {
$event?.stopPropagation();
}
// safeExecute(parseFunction(descriptor.customFunction, ['$event', 'widgetContext']), [$event, this.ctx])
// safeExecute(parseFunction(descriptor.customFunction, ['$event', 'widgetContext']), [$event, this.ctx])
const entityInfo = this.ctx.actionsApi.getActiveEntityInfo();
const entityId = entityInfo ? entityInfo.entityId : null;
const entityName = entityInfo ? entityInfo.entityName : null;
@ -153,13 +172,17 @@ export class MapWidgetController implements MapWidgetInterface {
}]
);
})).subscribe(res => {
console.log('MapWidgetController -> setMarkerLocation -> res', res)
});
}
initSettings(settings: UnitedMapSettings): UnitedMapSettings {
const functionParams = ['data', 'dsData', 'dsIndex'];
this.provider = settings.provider || this.mapProvider;
if (!settings.mapProviderHere) {
if (settings.mapProvider && hereProviders.includes(settings.mapProvider))
settings.mapProviderHere = settings.mapProvider
else settings.mapProviderHere = hereProviders[0];
}
const customOptions = {
provider: this.provider,
mapUrl: settings?.mapImageUrl,
@ -173,7 +196,7 @@ export class MapWidgetController implements MapWidgetInterface {
'<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' +
settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}',
defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition),
currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? {
currentImage: (settings.markerImage?.length) ? {
url: settings.markerImage,
size: settings.markerImageSize || 34
} : null
@ -253,7 +276,7 @@ export const defaultSettings: any = {
useDefaultCenterPosition: false,
showTooltipAction: 'click',
autocloseTooltip: false,
showPolygon: true,
showPolygon: false,
labelColor: '#000000',
color: '#FE7569',
polygonColor: '#0000ff',
@ -267,10 +290,16 @@ export const defaultSettings: any = {
strokeOpacity: 1.0,
initCallback: () => { },
defaultZoomLevel: 8,
dontFitMapBounds: false,
disableScrollZooming: false,
minZoomLevel: 16,
credentials: '',
markerClusteringSetting: null,
draggableMarker: false
draggableMarker: false,
fitMapBounds: true
};
export const hereProviders = [
'HERE.normalDay',
'HERE.normalNight',
'HERE.hybridDay',
'HERE.terrainDay']

View File

@ -17,34 +17,32 @@
import L from 'leaflet';
import _ from 'lodash';
import { MarkerSettings, PolylineSettings, PolygonSettings } from './map-models';
import { parseWithTranslation } from '@app/core/utils';
export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings,
content?: string | HTMLElement): L.Popup {
const popup = L.popup();
popup.setContent(content);
console.log(settings);
target.bindPopup(popup, { autoClose: settings.autocloseTooltip, closeOnClick: false });
target.on('popupopen', () => {
let actions = document.getElementsByClassName('tb-custom-action');
Array.from(actions).forEach(
(element: HTMLElement) => {
if (element && settings.tooltipActions[element.id]) {
console.log(settings.tooltipActions[element.id]);
element.addEventListener('click', settings.tooltipActions[element.id])
}
})
})
if (settings.displayTooltipAction === 'hover') {
if (settings.showTooltipAction === 'hover') {
target.off('click');
target.on('mouseover', function () {
this.openPopup();
target.openPopup();
});
target.on('mouseout', function () {
this.closePopup();
target.closePopup();
});
}
target.on('popupopen', () => {
const actions = document.getElementsByClassName('tb-custom-action');
Array.from(actions).forEach(
(element: HTMLElement) => {
if (element && settings.tooltipAction[element.id]) {
element.addEventListener('click', settings.tooltipAction[element.id])
}
});
});
return popup;
}

View File

@ -29,3 +29,7 @@
background: none;
box-shadow: none;
}
.leaflet-container{
background-color: white;
}

View File

@ -14,13 +14,13 @@
/// limitations under the License.
///
import L from 'leaflet';
import { MarkerSettings, FormattedData } from './map-models';
import { aspectCache, safeExecute, parseTemplate } from '@app/core/utils';
import { aspectCache, parseWithTranslation, safeExecute } from '@app/core/utils';
import L, { LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models';
import { createTooltip } from './maps-utils';
import tinycolor from 'tinycolor2';
export class Marker {
leafletMarker: L.Marker;
tooltipOffset: [number, number];
tooltip: L.Popup;
@ -28,8 +28,8 @@ export class Marker {
data: FormattedData;
dataSources: FormattedData[];
constructor(private map: L.Map, location: L.LatLngExpression, public settings: MarkerSettings,
data?, dataSources?, onClickListener?, onDragendListener?) {
constructor(location: L.LatLngExpression, public settings: MarkerSettings,
data?: FormattedData, dataSources?, onDragendListener?) {
this.setDataSources(data, dataSources);
this.leafletMarker = L.marker(location, {
draggable: settings.draggableMarker
@ -39,15 +39,21 @@ export class Marker {
this.leafletMarker.setIcon(iconInfo.icon);
this.tooltipOffset = [0, -iconInfo.size[1] + 10];
this.updateMarkerLabel(settings);
this.leafletMarker.addTo(map);
});
if (settings.showTooltip) {
this.tooltip = createTooltip(this.leafletMarker, settings, parseTemplate(this.settings.tooltipPattern, data));
this.tooltip = createTooltip(this.leafletMarker, settings);
this.updateMarkerTooltip(data);
}
if (onClickListener) {
this.leafletMarker.on('click', onClickListener);
if (this.settings.markerClick) {
this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.markerClick) {
if (typeof (this.settings.markerClick[action]) === 'function') {
this.settings.markerClick[action](event);
}
}
});
}
if (onDragendListener) {
@ -61,7 +67,9 @@ export class Marker {
}
updateMarkerTooltip(data: FormattedData) {
this.tooltip.setContent(parseTemplate(this.settings.tooltipPattern, data));
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));
}
updateMarkerPosition(position: L.LatLngExpression) {
@ -70,13 +78,10 @@ export class Marker {
updateMarkerLabel(settings: MarkerSettings) {
this.leafletMarker.unbindTooltip();
if (settings.showLabel) {
if (settings.useLabelFunction) {
settings.labelText = parseTemplate(
safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]), this.data)
}
else settings.labelText = parseTemplate(settings.label, this.data);
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.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
}
@ -97,7 +102,6 @@ export class Marker {
}
createMarkerIcon(onMarkerIconReady) {
if (this.settings.icon) {
onMarkerIconReady({
size: [30, 30],
@ -105,11 +109,11 @@ export class Marker {
});
return;
}
const currentImage = this.settings.useMarkerImageFunction ?
safeExecute(this.settings.markerImageFunction,
[this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex();
if (currentImage && currentImage.url) {
aspectCache(currentImage.url).subscribe(
@ -136,19 +140,19 @@ export class Marker {
};
onMarkerIconReady(iconInfo);
} else {
this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady);
this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
}
}
);
} else {
this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady);
this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
}
}
createDefaultMarkerIcon(color, onMarkerIconReady) {
const pinColor = color.substr(1);
const icon = L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
iconSize: [21, 34],
iconAnchor: [10, 34],
popupAnchor: [0, -34],

View File

@ -20,14 +20,9 @@ import { MapSettings, UnitedMapSettings } from '../map-models';
export class HEREMap extends LeafletMap {
constructor($container, options: UnitedMapSettings) {
const defaultCredentials =
{
app_id: 'AhM6TzD9ThyK78CT3ptx',
app_code: 'p6NPiITB3Vv0GMUFnkLOOg'
}
super($container, options);
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
const tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'HERE.normalDay', options.credentials || defaultCredentials);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
tileLayer.addTo(map);
super.setMap(map);
super.initSettings(options);

View File

@ -110,7 +110,7 @@ export const hereMapSettingsSchema =
title: 'HERE Map Configuration',
type: 'object',
properties: {
mapProvider: {
mapProviderHere: {
title: 'Map layer',
type: 'string',
default: 'HERE.normalDay'
@ -120,11 +120,13 @@ export const hereMapSettingsSchema =
properties: {
app_id: {
title: 'HERE app id',
type: 'string'
type: 'string',
default: 'AhM6TzD9ThyK78CT3ptx'
},
app_code: {
title: 'HERE app code',
type: 'string'
type: 'string',
default: 'p6NPiITB3Vv0GMUFnkLOOg'
}
},
required: ['app_id', 'app_code']
@ -134,7 +136,7 @@ export const hereMapSettingsSchema =
},
form: [
{
key: 'mapProvider',
key: 'mapProviderHere',
type: 'rc-select',
multiple: false,
items: [
@ -339,43 +341,6 @@ export const commonMapSettingsSchema =
type: 'boolean',
default: false
},
polygonKeyName: {
title: 'Polygon key name',
type: 'string',
default: 'coordinates'
},
polygonColor: {
title: 'Polygon color',
type: 'string'
},
polygonOpacity: {
title: 'Polygon opacity',
type: 'number',
default: 0.5
},
polygonStrokeColor: {
title: 'Stroke color',
type: 'string'
},
polygonStrokeOpacity: {
title: 'Stroke opacity',
type: 'number',
default: 1
},
polygonStrokeWeight: {
title: 'Stroke weight',
type: 'number',
default: 1
},
usePolygonColorFunction: {
title: 'Use polygon color function',
type: 'boolean',
default: false
},
polygonColorFunction: {
title: 'Polygon Color function: f(data, dsData, dsIndex)',
type: 'string'
},
markerImage: {
title: 'Custom marker image',
type: 'string'
@ -455,20 +420,6 @@ export const commonMapSettingsSchema =
{
key: 'colorFunction',
type: 'javascript'
}, 'showPolygon', 'polygonKeyName',
{
key: 'polygonColor',
type: 'color'
},
'polygonOpacity',
{
key: 'polygonStrokeColor',
type: 'color'
},
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
{
key: 'polygonColorFunction',
type: 'javascript'
},
{
key: 'markerImage',
@ -488,7 +439,73 @@ export const commonMapSettingsSchema =
type: 'image'
}
]
}
},
'showPolygon',
]
};
export const mapPolygonSchema =
{
schema: {
title: 'Map Polygon Configuration',
type: 'object',
properties: {
polygonKeyName: {
title: 'Polygon key name',
type: 'string',
default: 'coordinates'
},
polygonColor: {
title: 'Polygon color',
type: 'string'
},
polygonOpacity: {
title: 'Polygon opacity',
type: 'number',
default: 0.5
},
polygonStrokeColor: {
title: 'Stroke color',
type: 'string'
},
polygonStrokeOpacity: {
title: 'Stroke opacity',
type: 'number',
default: 1
},
polygonStrokeWeight: {
title: 'Stroke weight',
type: 'number',
default: 1
},
usePolygonColorFunction: {
title: 'Use polygon color function',
type: 'boolean',
default: false
},
polygonColorFunction: {
title: 'Polygon Color function: f(data, dsData, dsIndex)',
type: 'string'
},
},
required: []
},
form: [
'polygonKeyName',
{
key: 'polygonColor',
type: 'color'
},
'polygonOpacity',
{
key: 'polygonStrokeColor',
type: 'color'
},
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
{
key: 'polygonColorFunction',
type: 'javascript'
},
]
};
@ -527,23 +544,12 @@ export const markerClusteringSettingsSchema =
title: 'Use map markers clustering',
type: 'boolean',
default: false
},
zoomOnClick: {
title: 'Zoom when clicking on a cluster',
type: 'boolean',
default: true
},
maxZoom: {
title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',
type: 'number'
}
},
required: []
},
form: [
'useClusterMarkers',
'zoomOnClick',
'maxZoom'
]
};
@ -571,12 +577,23 @@ export const markerClusteringSettingsSchemaGoogle =
]
};
export const markerClusteringSettingsSchemaLeaflet =
{
schema: {
title: 'Markers Clustering Configuration Leaflet',
type: 'object',
properties: {
zoomOnClick: {
title: 'Zoom when clicking on a cluster',
type: 'boolean',
default: true
},
maxZoom: {
title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',
type: 'number'
},
showCoverageOnHover: {
title: 'Show the bounds of markers when mouse over a cluster',
type: 'boolean',
@ -606,6 +623,8 @@ export const markerClusteringSettingsSchemaLeaflet =
required: []
},
form: [
'zoomOnClick',
'maxZoom',
'showCoverageOnHover',
'animate',
'maxClusterRadius',

View File

@ -17,17 +17,17 @@
-->
<div class="trip-animation-widget">
<div class="trip-animation-label-container" *ngIf="settings.showLabel">
{{settings.label | tbParseTemplate: activeTrip}}
{{label }}
</div>
<div class="trip-animation-container" layout="column">
<div class="trip-animation-container" fxLayout="column">
<div class="map" #map></div>
<div class="trip-animation-info-panel" layout="row">
<div class="trip-animation-info-panel" fxLayout="row">
<button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip"
*ngIf="settings.showTooltip" (click)="showHideTooltip()">
*ngIf="settings.showTooltip" (click)="visibleTooltip = !visibleTooltip">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="trip-animation-tooltip md-whiteframe-z4" layout="column"
<div class="trip-animation-tooltip md-whiteframe-z4" fxLayout="column"
[ngClass]="{'trip-animation-tooltip-hidden':!visibleTooltip}" [innerHTML]="mainTooltip"
[ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
</div>

View File

@ -74,10 +74,10 @@
.trip-animation-tooltip {
position: absolute;
top: 38px;
top: 30px;
right: 0;
z-index: 400;
padding: 10px;
z-index: 1000;
padding: 5px;
background-color: #fff;
transition: 0.3s ease-in-out;
@ -86,4 +86,4 @@
}
}
}
}
}

View File

@ -22,13 +22,14 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef, SecurityContext } from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
import { MapProviders } from '../lib/maps/map-models';
import { parseArray, parseTemplate, safeExecute } from '@app/core/utils';
import { parseArray, parseWithTranslation, safeExecute, parseTemplate } from '@app/core/utils';
import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
import { tripAnimationSchema } from '../lib/maps/schemes';
import { DomSanitizer } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getRatio, findAngle } from '../lib/maps/maps-utils';
import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
import moment from 'moment';
@Component({
@ -55,6 +56,9 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
mainTooltip = '';
visibleTooltip = false;
activeTrip;
label;
minTime;
maxTime;
static getSettingsSchema(): JsonSettingsSchema {
const schema = initSchema();
@ -90,48 +94,58 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
ngAfterViewInit() {
const ctxCopy: WidgetContext = _.cloneDeep(this.ctx);
ctxCopy.settings.showLabel = false;
ctxCopy.settings.showTooltip = false;
this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement);
}
timeUpdated(time: number) {
const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
this.activeTrip = currentPosition[0];
if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedData);
this.minTime = moment(this.historicalData[0][this.historicalData.length - 1]?.time).format('YYYY-MM-DD HH:mm:ss')
this.maxTime = moment(this.historicalData[0][0]?.time).format('YYYY-MM-DD HH:mm:ss')
this.calcLabel();
this.calcTooltip();
if (this.mapWidget) {
if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedData);
}
this.mapWidget.map.updateMarkers(currentPosition);
}
this.mapWidget.map.updateMarkers(currentPosition);
}
setActiveTrip() {
}
calculateIntervals() {
this.historicalData.forEach((dataSource, index) => {
this.intervals = [];
for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
this.intervals.push(time);
}
this.intervals.push(dataSource[dataSource.length - 1]?.time);
this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
});
}
showHideTooltip() {
calcTooltip() {
const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
const tooltipText: string = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooolTipFunction, [this.activeTrip, this.historicalData, 0])
: this.settings.tooltipPattern;
safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
this.mainTooltip = this.sanitizer.sanitize(
SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true)));
}
this.mainTooltip = this.sanitizer.sanitize(SecurityContext.HTML, parseTemplate(tooltipText, this.activeTrip))
this.visibleTooltip = !this.visibleTooltip;
calcLabel() {
const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
const labelText: string = this.settings.useLabelFunction ?
safeExecute(this.settings.labelFunction, [data, this.historicalData, 0]) : this.settings.label;
this.label = (parseWithTranslation.parseTemplate(labelText, data, true));
}
interpolateArray(originData, interpolatedIntervals) {
const result = {};
for (let i = 1, j = 0; i < originData.length && j < interpolatedIntervals.length;) {
const currentTime = interpolatedIntervals[j];
while (originData[i].time < currentTime) i++;

View File

@ -16,15 +16,21 @@
-->
<div class="trip-animation-control-panel">
<div>
<div fxFlex fxLayout="row" fxFlexAlign="center">
<button mat-icon-button class="mat-icon-button" aria-label="Start" (click)="moveStart()">
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">fast_rewind</mat-icon>
</button>
<button mat-icon-button class="mat-icon-button" aria-label="Previous" (click)="movePrev()">
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_previous</mat-icon>
</button>
<mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
</mat-slider>
<div fxLayout="column" fxFlex="100">
<mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
</mat-slider>
<div class="panel-timer">
<span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
<span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
</div>
</div>
<button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()">
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_next</mat-icon>
</button>
@ -41,11 +47,8 @@
pause_circle_outline
</mat-icon>
</button>
<mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select" aria-label="Speed selector">
<mat-option [value]="speedValue" flex="1" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
<mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
aria-label="Speed selector">
<mat-option [value]="speedValue" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
</mat-select>
</div>
<div class="panel-timer">
<span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
<span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
</div>
</div>

View File

@ -13,128 +13,120 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.trip-animation-widget {
.trip-animation-label-container {
height: 24px;
}
.trip-animation-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
height: 100%;
font-size: 16px;
line-height: 24px;
.trip-animation-label-container {
height: 24px;
}
.trip-animation-container {
position: relative;
#trip-animation-map {
z-index: 1;
flex: 1;
width: 100%;
height: 100%;
#trip-animation-map {
z-index: 1;
width: 100%;
height: 100%;
.pointsLayerMarkerIcon {
border-radius: 50%;
}
.pointsLayerMarkerIcon {
border-radius: 50%;
}
}
.trip-animation-info-panel {
position: absolute;
.trip-animation-info-panel {
position: absolute;
top: 0;
right: 0;
z-index: 2;
pointer-events: none;
.mat-button {
top: 0;
right: 0;
z-index: 2;
pointer-events: none;
left: 0;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
padding: 0 0 2px;
margin: 2px;
line-height: 24px;
.md-button {
top: 0;
left: 0;
width: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
padding: 0 0 2px;
margin: 2px;
line-height: 24px;
ng-mat-icon {
width: 24px;
height: 24px;
ng-md-icon {
width: 24px;
height: 24px;
svg {
width: inherit;
height: inherit;
}
svg {
width: inherit;
height: inherit;
}
}
}
.trip-animation-tooltip {
position: absolute;
top: 38px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
transition: 0.3s ease-in-out;
&-hidden {
transform: translateX(110%);
}
}
}
.trip-animation-control-panel {
position: relative;
box-sizing: border-box;
width: 100%;
padding-bottom: 16px;
padding-left: 10px;
.trip-animation-tooltip {
position: absolute;
top: 38px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
transition: 0.3s ease-in-out;
md-slider-container {
md-slider {
min-width: 80px;
}
button.md-button.md-icon-button {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
margin: 0;
line-height: 28px;
md-icon {
width: 28px;
height: 28px;
font-size: 28px;
svg {
width: inherit;
height: inherit;
}
}
}
md-select {
margin: 0;
}
}
.panel-timer {
max-width: none;
padding-right: 250px;
padding-left: 90px;
margin-top: -20px;
font-size: 12px;
font-weight: 500;
text-align: center;
&-hidden {
transform: translateX(110%);
}
}
}
.speed-select{
.trip-animation-control-panel {
position: relative;
box-sizing: border-box;
width: 100%;
padding-bottom: 16px;
padding-left: 10px;
mat-slider-container {
mat-slider {
min-width: 80px;
}
button.mat-button.mat-icon-button {
width: 44px;
min-width: 44px;
height: 44px;
min-height: 44px;
margin: 0;
line-height: 28px;
mat-icon {
width: 28px;
height: 28px;
font-size: 28px;
svg {
width: inherit;
height: inherit;
}
}
}
mat-select {
margin: 0;
}
}
.panel-timer {
max-width: none;
margin-top: -20px;
font-size: 12px;
font-weight: 500;
text-align: center;
}
}
.speed-select {
width: 50px;
margin-left: 20px;
margin-left: 10px;
margin-top: 10px;
}

View File

@ -15,8 +15,8 @@
///
import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { interval, Subscription, Subscriber, SubscriptionLike, Observer } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { interval } from 'rxjs';
import { filter } from 'rxjs/operators';
import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models';
@Component({

View File

@ -15,11 +15,11 @@
///
import { Pipe, PipeTransform } from '@angular/core';
import { parseTemplate } from '@app/core/utils';
import { parseTemplate, parseWithTranslation } from '@app/core/utils';
@Pipe({ name: 'tbParseTemplate' })
export class TbTemplatePipe implements PipeTransform {
transform(template, data): string {
return parseTemplate(template, data);
return parseWithTranslation.parseTemplate(template, data);
}
}