Commit c04a86e914541c8021a8bccbfd3c32176b9eeff3

Authored by Artem Halushko
1 parent 78c0e519

add label to trip animation

@@ -498,7 +498,7 @@ export function safeExecute(func: Function, params = []) { @@ -498,7 +498,7 @@ export function safeExecute(func: Function, params = []) {
498 res = func(...params); 498 res = func(...params);
499 } 499 }
500 catch (err) { 500 catch (err) {
501 - console.log(err); 501 + console.log('error in external function:', err);
502 res = null; 502 res = null;
503 } 503 }
504 } 504 }
@@ -519,7 +519,7 @@ export function parseFunction(source: string, params: string[] = []): Function { @@ -519,7 +519,7 @@ export function parseFunction(source: string, params: string[] = []): Function {
519 return res; 519 return res;
520 } 520 }
521 521
522 -export function parseTemplate(template, data) { 522 +export function parseTemplate(template: string, data: object) {
523 let res = ''; 523 let res = '';
524 try { 524 try {
525 let variables = ''; 525 let variables = '';
@@ -27,7 +27,6 @@ import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; @@ -27,7 +27,6 @@ import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
27 import { filter } from 'rxjs/operators'; 27 import { filter } from 'rxjs/operators';
28 import { Polyline } from './polyline'; 28 import { Polyline } from './polyline';
29 import { Polygon } from './polygon'; 29 import { Polygon } from './polygon';
30 -import { string } from 'prop-types';  
31 30
32 export default abstract class LeafletMap { 31 export default abstract class LeafletMap {
33 32
@@ -43,7 +42,6 @@ export default abstract class LeafletMap { @@ -43,7 +42,6 @@ export default abstract class LeafletMap {
43 isMarketCluster; 42 isMarketCluster;
44 bounds: L.LatLngBounds; 43 bounds: L.LatLngBounds;
45 44
46 -  
47 constructor($container: HTMLElement, options: MapOptions) { 45 constructor($container: HTMLElement, options: MapOptions) {
48 this.options = options; 46 this.options = options;
49 } 47 }
@@ -138,7 +136,7 @@ export default abstract class LeafletMap { @@ -138,7 +136,7 @@ export default abstract class LeafletMap {
138 } 136 }
139 137
140 invalidateSize() { 138 invalidateSize() {
141 - this.map.invalidateSize(true); 139 + this.map?.invalidateSize(true);
142 } 140 }
143 141
144 onResize() { 142 onResize() {
@@ -160,7 +158,7 @@ export default abstract class LeafletMap { @@ -160,7 +158,7 @@ export default abstract class LeafletMap {
160 } 158 }
161 } 159 }
162 160
163 - ////Markers 161 + //Markers
164 updateMarkers(markersData) { 162 updateMarkers(markersData) {
165 markersData.forEach(data => { 163 markersData.forEach(data => {
166 if (data.rotationAngle) { 164 if (data.rotationAngle) {
@@ -259,7 +257,6 @@ export default abstract class LeafletMap { @@ -259,7 +257,6 @@ export default abstract class LeafletMap {
259 257
260 createPolygon(data, dataSources, settings) { 258 createPolygon(data, dataSources, settings) {
261 this.ready$.subscribe(() => { 259 this.ready$.subscribe(() => {
262 - //public map, coordinates, dataSources, settings, onClickListener?  
263 this.polygon = new Polygon(this.map, data, dataSources, settings); 260 this.polygon = new Polygon(this.map, data, dataSources, settings);
264 const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds()); 261 const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds());
265 if (bounds.isValid()) { 262 if (bounds.isValid()) {
@@ -91,6 +91,15 @@ export class MapWidgetController implements MapWidgetInterface { @@ -91,6 +91,15 @@ export class MapWidgetController implements MapWidgetInterface {
91 initSettings(settings: any) { 91 initSettings(settings: any) {
92 const functionParams = ['data', 'dsData', 'dsIndex']; 92 const functionParams = ['data', 'dsData', 'dsIndex'];
93 this.provider = settings.provider ? settings.provider : this.mapProvider; 93 this.provider = settings.provider ? settings.provider : this.mapProvider;
  94 +
  95 + function getDefCenterPosition(position) {
  96 + if (typeof (position) === 'string')
  97 + return position.split(',');
  98 + if (typeof (position) === 'object')
  99 + return position;
  100 + return [0, 0];
  101 + }
  102 +
94 const customOptions = { 103 const customOptions = {
95 provider: this.provider, 104 provider: this.provider,
96 mapUrl: settings?.mapImageUrl, 105 mapUrl: settings?.mapImageUrl,
@@ -102,7 +111,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -102,7 +111,7 @@ export class MapWidgetController implements MapWidgetInterface {
102 labelColor: this.ctx.widgetConfig.color, 111 labelColor: this.ctx.widgetConfig.color,
103 tooltipPattern: settings.tooltipPattern || 112 tooltipPattern: settings.tooltipPattern ||
104 "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${" + settings.latKeyName + ":7}<br/><b>Longitude:</b> ${" + settings.lngKeyName + ":7}", 113 "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${" + settings.latKeyName + ":7}<br/><b>Longitude:</b> ${" + settings.lngKeyName + ":7}",
105 - defaultCenterPosition: settings?.defaultCenterPosition?.split(',') || [0, 0], 114 + defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition),
106 useDraggableMarker: true, 115 useDraggableMarker: true,
107 currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? { 116 currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? {
108 url: settings.markerImage, 117 url: settings.markerImage,
@@ -137,7 +146,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -137,7 +146,7 @@ export class MapWidgetController implements MapWidgetInterface {
137 return {}; 146 return {};
138 } 147 }
139 148
140 - public static getProvidersSchema() { 149 + public static getProvidersSchema() {
141 return mergeSchemes([mapProviderSchema, 150 return mergeSchemes([mapProviderSchema,
142 ...Object.values(providerSets)?.map( 151 ...Object.values(providerSets)?.map(
143 setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]); 152 setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]);
@@ -246,5 +255,5 @@ const defaultSettings = { @@ -246,5 +255,5 @@ const defaultSettings = {
246 minZoomLevel: 16, 255 minZoomLevel: 16,
247 credentials: '', 256 credentials: '',
248 markerClusteringSetting: null, 257 markerClusteringSetting: null,
249 - draggebleMarker: true 258 + draggebleMarker: false
250 } 259 }
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 /// 15 ///
16 16
17 import L from 'leaflet'; 17 import L from 'leaflet';
18 -import { interpolateOnPointSegment } from 'leaflet-geometryutil';  
19 import _ from 'lodash'; 18 import _ from 'lodash';
20 19
21 export function createTooltip(target, settings) { 20 export function createTooltip(target, settings) {
@@ -34,38 +33,3 @@ export function createTooltip(target, settings) { @@ -34,38 +33,3 @@ export function createTooltip(target, settings) {
34 return popup; 33 return popup;
35 } 34 }
36 35
37 -  
38 -export function interpolateArray(originData, interpolatedIntervals) {  
39 -  
40 - const getRatio = (firsMoment, secondMoment, intermediateMoment) => {  
41 - return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);  
42 - };  
43 -  
44 - function findAngle(startPoint, endPoint) {  
45 - let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude);  
46 - angle = angle * 180 / Math.PI;  
47 - return parseInt(angle.toFixed(2));  
48 - }  
49 -  
50 - const result = {};  
51 -  
52 - for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) {  
53 - const currentTime = interpolatedIntervals[j];  
54 - while (originData[i].time < currentTime) i++;  
55 - const before = originData[i - 1];  
56 - const after = originData[i];  
57 - const interpolation = interpolateOnPointSegment(  
58 - new L.Point(before.latitude, before.longitude),  
59 - new L.Point(after.latitude, after.longitude),  
60 - getRatio(before.time, after.time, currentTime));  
61 - result[currentTime] = ({  
62 - ...originData[i],  
63 - rotationAngle: findAngle(before, after),  
64 - latitude: interpolation.x,  
65 - longitude: interpolation.y  
66 - });  
67 - j++;  
68 - }  
69 -  
70 - return result;  
71 -};  
@@ -56,7 +56,6 @@ export class Marker { @@ -56,7 +56,6 @@ export class Marker {
56 if (onDragendListener) { 56 if (onDragendListener) {
57 this.leafletMarker.on('dragend', onDragendListener); 57 this.leafletMarker.on('dragend', onDragendListener);
58 } 58 }
59 -  
60 } 59 }
61 60
62 setDataSources(data, dataSources) { 61 setDataSources(data, dataSources) {
@@ -24,7 +24,6 @@ export class Polyline { @@ -24,7 +24,6 @@ export class Polyline {
24 data; 24 data;
25 25
26 constructor(private map: L.Map, locations, data, dataSources, settings) { 26 constructor(private map: L.Map, locations, data, dataSources, settings) {
27 - console.log("Polyline -> constructor -> data", data)  
28 this.dataSources = dataSources; 27 this.dataSources = dataSources;
29 this.data = data; 28 this.data = data;
30 this.leafletPoly = L.polyline(locations, 29 this.leafletPoly = L.polyline(locations,
@@ -34,7 +33,6 @@ export class Polyline { @@ -34,7 +33,6 @@ export class Polyline {
34 33
35 updatePolyline(settings, data, dataSources) { 34 updatePolyline(settings, data, dataSources) {
36 this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources)); 35 this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources));
37 -  
38 } 36 }
39 37
40 getPolyStyle(settings, data, dataSources): L.PolylineOptions { 38 getPolyStyle(settings, data, dataSources): L.PolylineOptions {
@@ -15,6 +15,23 @@ @@ -15,6 +15,23 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div class="map" #map ></div>  
19 -<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"  
20 - (onTimeUpdated)="timeUpdated($event)"></tb-history-selector> 18 +<div class="trip-animation-widget">
  19 + <div class="trip-animation-label-container" *ngIf="settings.showLabel">
  20 + {{settings.label | tbParseTemplate: activeTrip}}
  21 + </div>
  22 + <div class="trip-animation-container" layout="column">
  23 + <div class="map" #map></div>
  24 + <div class="trip-animation-info-panel" layout="row">
  25 + <button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip"
  26 + *ngIf="settings.showTooltip" (click)="showHideTooltip()">
  27 + <mat-icon>info_outline</mat-icon>
  28 + </button>
  29 + </div>
  30 + <div class="trip-animation-tooltip md-whiteframe-z4" layout="column"
  31 + [ngClass]="{ 'trip-animation-tooltip-hidden':!settings.showTooltip}" [innerHTML]="mainTooltip"
  32 + [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
  33 + </div>
  34 + </div>
  35 + <tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"
  36 + (onTimeUpdated)="timeUpdated($event)"></tb-history-selector>
  37 +</div>
@@ -14,8 +14,76 @@ @@ -14,8 +14,76 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
  17 +.trip-animation-widget {
  18 + position: relative;
  19 + width: 100%;
  20 + height: 100%;
  21 + font-size: 16px;
  22 + line-height: 24px;
  23 + display: flex;
  24 + flex-direction: column;
17 25
18 -.map{ 26 + .trip-animation-label-container {
  27 + height: 24px;
  28 + }
  29 +
  30 + .trip-animation-container {
  31 + position: relative;
  32 + z-index: 1;
  33 + flex: 1;
19 width: 100%; 34 width: 100%;
20 - height: calc(100% - 100px);  
21 -}  
  35 +
  36 + .map {
  37 + width: 100%;
  38 + height: 100%;
  39 + }
  40 +
  41 + .trip-animation-info-panel {
  42 + position: absolute;
  43 + top: 0;
  44 + right: 0;
  45 + pointer-events: none;
  46 +
  47 + .tooltip-button {
  48 + top: 0;
  49 + left: 0;
  50 + width: 32px;
  51 + min-width: 32px;
  52 + height: 32px;
  53 + min-height: 32px;
  54 + padding: 0 0 2px;
  55 + margin: 2px;
  56 + line-height: 24px;
  57 + z-index: 999;
  58 +
  59 + &::ng-deep .mat-button-wrapper {
  60 + padding: 0;
  61 + }
  62 +
  63 + mat-icon {
  64 + width: 24px;
  65 + height: 24px;
  66 +
  67 + svg {
  68 + width: inherit;
  69 + height: inherit;
  70 + }
  71 + }
  72 + }
  73 + }
  74 +
  75 + .trip-animation-tooltip {
  76 + position: absolute;
  77 + top: 38px;
  78 + right: 0;
  79 + z-index: 2;
  80 + padding: 10px;
  81 + background-color: #fff;
  82 + transition: 0.3s ease-in-out;
  83 +
  84 + &-hidden {
  85 + transform: translateX(110%);
  86 + }
  87 + }
  88 + }
  89 +}
@@ -14,15 +14,19 @@ @@ -14,15 +14,19 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
  17 +import L from 'leaflet';
  18 +import _ from 'lodash';
  19 +import tinycolor from "tinycolor2";
  20 +import { interpolateOnPointSegment } from 'leaflet-geometryutil';
  21 +
17 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; 22 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core';
18 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; 23 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
19 import { MapProviders } from '../lib/maps/map-models'; 24 import { MapProviders } from '../lib/maps/map-models';
20 import { parseArray } from '@app/core/utils'; 25 import { parseArray } from '@app/core/utils';
21 -import { interpolateArray } from '../lib/maps/maps-utils';  
22 -import tinycolor from "tinycolor2";  
23 import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; 26 import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
24 import { tripAnimationSchema } from '../lib/maps/schemes'; 27 import { tripAnimationSchema } from '../lib/maps/schemes';
25 28
  29 +
26 @Component({ 30 @Component({
27 selector: 'trip-animation', 31 selector: 'trip-animation',
28 templateUrl: './trip-animation.component.html', 32 templateUrl: './trip-animation.component.html',
@@ -41,6 +45,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -41,6 +45,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
41 interpolatedData = []; 45 interpolatedData = [];
42 widgetConfig; 46 widgetConfig;
43 settings; 47 settings;
  48 + mainTooltip;
  49 + activeTrip;
44 50
45 constructor(private cd: ChangeDetectorRef) { } 51 constructor(private cd: ChangeDetectorRef) { }
46 52
@@ -57,19 +63,27 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -57,19 +63,27 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
57 let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; 63 let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
58 if (subscription) subscription.callbacks.onDataUpdated = (updated) => { 64 if (subscription) subscription.callbacks.onDataUpdated = (updated) => {
59 this.historicalData = parseArray(this.ctx.data); 65 this.historicalData = parseArray(this.ctx.data);
  66 + this.activeTrip = this.historicalData[0][0];
60 this.calculateIntervals(); 67 this.calculateIntervals();
61 this.timeUpdated(this.intervals[0]); 68 this.timeUpdated(this.intervals[0]);
62 - this.mapWidget.map.map.invalidateSize(); 69 + this.mapWidget.map.map?.invalidateSize();
63 this.cd.detectChanges(); 70 this.cd.detectChanges();
64 } 71 }
65 } 72 }
66 73
67 ngAfterViewInit() { 74 ngAfterViewInit() {
68 - this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); 75 + let ctxCopy = _.cloneDeep(this.ctx);
  76 + ctxCopy.settings.showLabel = false;
  77 + this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement);
69 } 78 }
70 79
71 timeUpdated(time) { 80 timeUpdated(time) {
72 const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]); 81 const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
  82 + this.activeTrip = currentPosition[0];
  83 + this.mapWidget.map.updatePolylines(this.interpolatedData);
  84 + if (this.settings.showPolygon) {
  85 + this.mapWidget.map.updatePolygons(this.interpolatedData);
  86 + }
73 this.mapWidget.map.updateMarkers(currentPosition); 87 this.mapWidget.map.updateMarkers(currentPosition);
74 } 88 }
75 89
@@ -80,10 +94,47 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -80,10 +94,47 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
80 this.intervals.push(time); 94 this.intervals.push(time);
81 } 95 }
82 this.intervals.push(dataSource[dataSource.length - 1]?.time); 96 this.intervals.push(dataSource[dataSource.length - 1]?.time);
83 - this.interpolatedData[index] = interpolateArray(dataSource, this.intervals); 97 + this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
84 }); 98 });
85 } 99 }
86 100
  101 + showHideTooltip() {
  102 + }
  103 +
  104 + interpolateArray(originData, interpolatedIntervals) {
  105 +
  106 + const getRatio = (firsMoment, secondMoment, intermediateMoment) => {
  107 + return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
  108 + };
  109 +
  110 + function findAngle(startPoint, endPoint) {
  111 + let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude);
  112 + angle = angle * 180 / Math.PI;
  113 + return parseInt(angle.toFixed(2));
  114 + }
  115 +
  116 + const result = {};
  117 +
  118 + for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) {
  119 + const currentTime = interpolatedIntervals[j];
  120 + while (originData[i].time < currentTime) i++;
  121 + const before = originData[i - 1];
  122 + const after = originData[i];
  123 + const interpolation = interpolateOnPointSegment(
  124 + new L.Point(before.latitude, before.longitude),
  125 + new L.Point(after.latitude, after.longitude),
  126 + getRatio(before.time, after.time, currentTime));
  127 + result[currentTime] = ({
  128 + ...originData[i],
  129 + rotationAngle: findAngle(before, after) + this.settings.rotationAngle,
  130 + latitude: interpolation.x,
  131 + longitude: interpolation.y
  132 + });
  133 + j++;
  134 + }
  135 + return result;
  136 + };
  137 +
87 static getSettingsSchema() { 138 static getSettingsSchema() {
88 let schema = initSchema(); 139 let schema = initSchema();
89 addToSchema(schema, TbMapWidgetV2.getProvidersSchema()); 140 addToSchema(schema, TbMapWidgetV2.getProvidersSchema());
@@ -20,3 +20,4 @@ export * from './keyboard-shortcut.pipe'; @@ -20,3 +20,4 @@ export * from './keyboard-shortcut.pipe';
20 export * from './milliseconds-to-time-string.pipe'; 20 export * from './milliseconds-to-time-string.pipe';
21 export * from './nospace.pipe'; 21 export * from './nospace.pipe';
22 export * from './truncate.pipe'; 22 export * from './truncate.pipe';
  23 +export * from './template.pipe';
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Pipe, PipeTransform } from '@angular/core';
  18 +import { parseTemplate } from '@app/core/utils';
  19 +
  20 +@Pipe({ name: 'tbParseTemplate' })
  21 +export class TbTemplatePipe implements PipeTransform {
  22 + transform(template, data): string {
  23 + console.log("TbTemplatePipe -> transform -> template, data", template, data)
  24 + return parseTemplate(template, data);
  25 + }
  26 +}
@@ -128,6 +128,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; @@ -128,6 +128,7 @@ import { LedLightComponent } from '@shared/components/led-light.component';
128 import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; 128 import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive";
129 import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; 129 import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component";
130 import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; 130 import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
  131 +import { TbTemplatePipe } from './pipe/public-api';
131 132
132 @NgModule({ 133 @NgModule({
133 providers: [ 134 providers: [
@@ -208,6 +209,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his @@ -208,6 +209,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his
208 HighlightPipe, 209 HighlightPipe,
209 TruncatePipe, 210 TruncatePipe,
210 TbJsonPipe, 211 TbJsonPipe,
  212 + TbTemplatePipe,
211 KeyboardShortcutPipe, 213 KeyboardShortcutPipe,
212 TbJsonToStringDirective, 214 TbJsonToStringDirective,
213 JsonObjectEditDialogComponent, 215 JsonObjectEditDialogComponent,
@@ -367,6 +369,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his @@ -367,6 +369,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his
367 HighlightPipe, 369 HighlightPipe,
368 TruncatePipe, 370 TruncatePipe,
369 TbJsonPipe, 371 TbJsonPipe,
  372 + TbTemplatePipe,
370 KeyboardShortcutPipe, 373 KeyboardShortcutPipe,
371 TranslateModule, 374 TranslateModule,
372 JsonObjectEditDialogComponent, 375 JsonObjectEditDialogComponent,