Browse Source

Merge branch 'feature/entity-data-query' of github.com:thingsboard/thingsboard into feature/entity-data-query

pull/3053/head
Andrii Shvaika 6 years ago
parent
commit
0533416982
  1. 3
      ui-ngx/src/app/core/api/widget-api.models.ts
  2. 7
      ui-ngx/src/app/core/api/widget-subscription.ts
  3. 202
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  4. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  5. 25
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  6. 4
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  7. 5
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

3
ui-ngx/src/app/core/api/widget-api.models.ts

@ -282,6 +282,9 @@ export interface IWidgetSubscription {
subscribe(): void; subscribe(): void;
subscribeAllForPaginatedData(pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): void;
subscribeForPaginatedData(datasourceIndex: number, subscribeForPaginatedData(datasourceIndex: number,
pageLink: EntityDataPageLink, pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): void; keyFilters: KeyFilter[]): void;

7
ui-ngx/src/app/core/api/widget-subscription.ts

@ -884,6 +884,13 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
} }
subscribeAllForPaginatedData(pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): void {
this.configuredDatasources.forEach((datasource, datasourceIndex) => {
this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters);
});
}
subscribeForPaginatedData(datasourceIndex: number, subscribeForPaginatedData(datasourceIndex: number,
pageLink: EntityDataPageLink, pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): void { keyFilters: KeyFilter[]): void {

202
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -32,8 +32,9 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { createTooltip, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; import { createTooltip, parseArray, parseData, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { DatasourceData } from '@shared/models/widget.models';
export default abstract class LeafletMap { export default abstract class LeafletMap {
@ -247,32 +248,75 @@ export default abstract class LeafletMap {
} }
} }
// Markers updateData(data: DatasourceData[], drawRoutes: boolean, showPolygon: boolean) {
updateMarkers(markersData: FormattedData[], callback?) { this.ready$.subscribe(() => {
markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => { if (drawRoutes) {
this.updatePolylines(parseArray(data), false);
}
if (showPolygon) {
this.updatePolygons(parseData(data), false);
}
this.updateMarkers(parseData(data), false);
this.updateBoundsInternal(drawRoutes, showPolygon);
});
}
private updateBoundsInternal(drawRoutes: boolean, showPolygon: boolean) {
this.bounds = new L.LatLngBounds(null, null);
if (drawRoutes) {
this.polylines.forEach((polyline) => {
this.bounds.extend(polyline.leafletPoly.getBounds());
});
}
if (showPolygon) {
this.polygons.forEach((polygon) => {
this.bounds.extend(polygon.leafletPoly.getBounds());
});
}
this.markers.forEach((marker) => {
this.bounds.extend(marker.leafletMarker.getLatLng());
});
this.fitBounds(this.bounds);
}
// Markers
updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata));
this.ready$.subscribe(() => {
const keys: string[] = [];
rawMarkers.forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) { if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ? const currentImage = this.options.useMarkerImageFunction ?
safeExecute(this.options.markerImageFunction, safeExecute(this.options.markerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({ this.options.icon = L.divIcon({
html: `<div class="arrow" html: `<div class="arrow"
style="transform: translate(-10px, -10px) style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg); rotate(${data.rotationAngle}deg);
${style}"><div>` ${style}"><div>`
}); });
} } else {
else { this.options.icon = null;
this.options.icon = null;
} }
if (this.markers.get(data.entityName)) { if (this.markers.get(data.entityName)) {
this.updateMarker(data.entityName, data, markersData, this.options) this.updateMarker(data.entityName, data, markersData, this.options)
} else {
this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
} }
else { keys.push(data.entityName);
this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, callback); });
const toDelete: string[] = [];
this.markers.forEach((v, mKey) => {
if (!keys.includes(mKey)) {
toDelete.push(mKey);
} }
});
toDelete.forEach((key) => {
this.deleteMarker(key);
});
this.markersData = markersData;
}); });
this.markersData = markersData;
} }
dragMarker = (e, data = {}) => { dragMarker = (e, data = {}) => {
@ -280,21 +324,20 @@ export default abstract class LeafletMap {
this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) }); this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
} }
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, callback?) { private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings,
this.ready$.subscribe(() => { updateBounds = true, callback?) {
const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
if (callback) if (callback)
newMarker.leafletMarker.on('click', () => { callback(data, true) }); newMarker.leafletMarker.on('click', () => { callback(data, true) });
if (this.bounds) if (this.bounds && updateBounds)
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
this.markers.set(key, newMarker); this.markers.set(key, newMarker);
if (this.options.useClusterMarkers) { if (this.options.useClusterMarkers) {
this.markersCluster.addLayer(newMarker.leafletMarker); this.markersCluster.addLayer(newMarker.leafletMarker);
} }
else { else {
this.map.addLayer(newMarker.leafletMarker); this.map.addLayer(newMarker.leafletMarker);
} }
});
} }
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
@ -306,8 +349,9 @@ export default abstract class LeafletMap {
if (settings.showTooltip) { if (settings.showTooltip) {
marker.updateMarkerTooltip(data); marker.updateMarkerTooltip(data);
} }
if (settings.useClusterMarkers) if (settings.useClusterMarkers) {
this.markersCluster.refreshClusters() this.markersCluster.refreshClusters()
}
marker.setDataSources(data, dataSources); marker.setDataSources(data, dataSources);
marker.updateMarkerIcon(settings); marker.updateMarkerIcon(settings);
} }
@ -315,7 +359,11 @@ export default abstract class LeafletMap {
deleteMarker(key: string) { deleteMarker(key: string) {
let marker = this.markers.get(key)?.leafletMarker; let marker = this.markers.get(key)?.leafletMarker;
if (marker) { if (marker) {
this.map.removeLayer(marker); if (this.options.useClusterMarkers) {
this.markersCluster.removeLayer(marker);
} else {
this.map.removeLayer(marker);
}
this.markers.delete(key); this.markers.delete(key);
marker = null; marker = null;
} }
@ -346,41 +394,49 @@ export default abstract class LeafletMap {
// Polyline // Polyline
updatePolylines(polyData: FormattedData[][], data?: FormattedData) { updatePolylines(polyData: FormattedData[][], updateBounds = true, data?: FormattedData) {
const keys: string[] = [];
polyData.forEach((dataSource: FormattedData[]) => { polyData.forEach((dataSource: FormattedData[]) => {
data = data || dataSource[0]; data = data || dataSource[0];
if (dataSource.length && data.entityName === dataSource[0].entityName) { if (dataSource.length && data.entityName === dataSource[0].entityName) {
if (this.polylines.get(data.entityName)) { if (this.polylines.get(data.entityName)) {
this.updatePolyline(data, dataSource, this.options); this.updatePolyline(data, dataSource, this.options, updateBounds);
} else {
this.createPolyline(data, dataSource, this.options, updateBounds);
} }
else { keys.push(data.entityName);
this.createPolyline(data, dataSource, this.options);
}
}
else {
if (data)
this.removePolyline(dataSource[0]?.entityName)
} }
}) });
const toDelete: string[] = [];
this.polylines.forEach((v, mKey) => {
if (!keys.includes(mKey)) {
toDelete.push(mKey);
}
});
toDelete.forEach((key) => {
this.removePolyline(key);
});
} }
createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) { createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
this.ready$.subscribe(() => { this.ready$.subscribe(() => {
const poly = new Polyline(this.map, const poly = new Polyline(this.map,
dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
const bounds = poly.leafletPoly.getBounds(); if (updateBounds) {
this.fitBounds(bounds); const bounds = poly.leafletPoly.getBounds();
this.fitBounds(bounds);
}
this.polylines.set(data.entityName, poly); this.polylines.set(data.entityName, poly);
}); });
} }
updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) { updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
this.ready$.subscribe(() => { this.ready$.subscribe(() => {
const poly = this.polylines.get(data.entityName); const poly = this.polylines.get(data.entityName);
const oldBounds = poly.leafletPoly.getBounds(); const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds(); const newBounds = poly.leafletPoly.getBounds();
if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) { if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds); this.fitBounds(newBounds);
} }
}); });
@ -399,40 +455,60 @@ export default abstract class LeafletMap {
// Polygon // Polygon
updatePolygons(polyData: FormattedData[]) { updatePolygons(polyData: FormattedData[], updateBounds = true) {
const keys: string[] = [];
polyData.forEach((data: FormattedData) => { polyData.forEach((data: FormattedData) => {
if (data && data.hasOwnProperty(this.options.polygonKeyName)) { if (data && data.hasOwnProperty(this.options.polygonKeyName)) {
if (typeof (data[this.options.polygonKeyName]) === 'string') { if (typeof (data[this.options.polygonKeyName]) === 'string') {
data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]) as LatLngTuple[]; data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]) as LatLngTuple[];
} }
if (this.polygons.get(data.$datasource.entityName)) { if (this.polygons.get(data.entityName)) {
this.updatePolygon(data, polyData, this.options); this.updatePolygon(data, polyData, this.options, updateBounds);
} } else {
else { this.createPolygon(data, polyData, this.options, updateBounds);
this.createPolygon(data, polyData, this.options);
} }
keys.push(data.entityName);
} }
}); });
const toDelete: string[] = [];
this.polygons.forEach((v, mKey) => {
if (!keys.includes(mKey)) {
toDelete.push(mKey);
}
});
toDelete.forEach((key) => {
this.removePolygon(key);
});
} }
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
this.ready$.subscribe(() => { this.ready$.subscribe(() => {
const polygon = new Polygon(this.map, polyData, dataSources, settings); const polygon = new Polygon(this.map, polyData, dataSources, settings);
const bounds = polygon.leafletPoly.getBounds(); if (updateBounds) {
this.fitBounds(bounds); const bounds = polygon.leafletPoly.getBounds();
this.polygons.set(polyData.$datasource.entityName, polygon); this.fitBounds(bounds);
}
this.polygons.set(polyData.entityName, polygon);
}); });
} }
updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
this.ready$.subscribe(() => { this.ready$.subscribe(() => {
const poly = this.polygons.get(polyData.entityName); const poly = this.polygons.get(polyData.entityName);
const oldBounds = poly.leafletPoly.getBounds(); const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolygon(polyData, dataSources, settings); poly.updatePolygon(polyData, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds(); const newBounds = poly.leafletPoly.getBounds();
if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) { if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds); this.fitBounds(newBounds);
} }
}); });
} }
removePolygon(name: string) {
const poly = this.polygons.get(name);
if (poly) {
this.map.removeLayer(poly.leafletPoly);
this.polygons.delete(name);
}
}
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -24,6 +24,8 @@ import {
googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema
} from './schemes'; } from './schemes';
export const DEFAULT_MAP_PAGE_SIZE = 1024;
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string; export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string;

25
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -14,7 +14,14 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { defaultSettings, hereProviders, MapProviders, providerSets, UnitedMapSettings } from './map-models'; import {
DEFAULT_MAP_PAGE_SIZE,
defaultSettings,
hereProviders,
MapProviders,
providerSets,
UnitedMapSettings
} from './map-models';
import LeafletMap from './leaflet-map'; import LeafletMap from './leaflet-map';
import { import {
commonMapSettingsSchema, commonMapSettingsSchema,
@ -35,6 +42,7 @@ import { AttributeService } from '@core/http/attribute.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import _ from 'lodash'; import _ from 'lodash';
import { EntityDataPageLink } from '@shared/models/query/query.models';
// @dynamic // @dynamic
export class MapWidgetController implements MapWidgetInterface { export class MapWidgetController implements MapWidgetInterface {
@ -73,6 +81,13 @@ export class MapWidgetController implements MapWidgetInterface {
if (this.settings.draggableMarker) { if (this.settings.draggableMarker) {
this.map.setDataSources(parseData(this.data)); this.map.setDataSources(parseData(this.data));
} }
this.pageLink = {
page: 0,
pageSize: DEFAULT_MAP_PAGE_SIZE,
textSearch: null,
dynamic: true
};
this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null);
} }
map: LeafletMap; map: LeafletMap;
@ -80,6 +95,7 @@ export class MapWidgetController implements MapWidgetInterface {
schema: JsonSettingsSchema; schema: JsonSettingsSchema;
data: DatasourceData[]; data: DatasourceData[];
settings: UnitedMapSettings; settings: UnitedMapSettings;
pageLink: EntityDataPageLink;
public static dataKeySettingsSchema(): object { public static dataKeySettingsSchema(): object {
return {}; return {};
@ -241,12 +257,7 @@ export class MapWidgetController implements MapWidgetInterface {
} }
update() { update() {
if (this.drawRoutes) this.map.updateData(this.data, this.drawRoutes, this.settings.showPolygon);
this.map.updatePolylines(parseArray(this.data));
if (this.settings.showPolygon) {
this.map.updatePolygons(parseData(this.data));
}
this.map.updateMarkers(parseData(this.data));
} }
resize() { resize() {

4
ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts

@ -161,14 +161,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
this.calcLabel(); this.calcLabel();
this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName)); this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName));
if (this.mapWidget) { if (this.mapWidget) {
this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), this.activeTrip); this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip);
if (this.settings.showPolygon) { if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedTimeData); this.mapWidget.map.updatePolygons(this.interpolatedTimeData);
} }
if (this.settings.showPoints) { if (this.settings.showPoints) {
this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip); this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip);
} }
this.mapWidget.map.updateMarkers(currentPosition, (trip) => { this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
this.activeTrip = trip; this.activeTrip = trip;
this.timeUpdated(this.currentTime) this.timeUpdated(this.currentTime)
}); });

5
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -714,9 +714,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
} }
private fetchEntityKeys(entityAliasId: string, query: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> { private fetchEntityKeys(entityAliasId: string, query: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> {
return this.aliasController.getAliasInfo(entityAliasId).pipe( return this.aliasController.resolveSingleEntityInfo(entityAliasId).pipe(
mergeMap((aliasInfo) => { mergeMap((entity) => {
const entity = aliasInfo.currentEntity;
if (entity) { if (entity) {
const fetchEntityTasks: Array<Observable<Array<DataKey>>> = []; const fetchEntityTasks: Array<Observable<Array<DataKey>>> = [];
for (const dataKeyType of dataKeyTypes) { for (const dataKeyType of dataKeyTypes) {

Loading…
Cancel
Save