@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
416 lines (410 loc) • 44.2 kB
JavaScript
import * as i0 from '@angular/core';
import { Input, Component, ViewChild, Inject, Optional } from '@angular/core';
import * as i5 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i1 from '@c8y/client';
import * as i2 from '@c8y/ngx-components';
import { C8yTranslateDirective, DatePipe, LoadingComponent, gettext, CommonModule as CommonModule$1, FormsModule as FormsModule$1, AssetLinkPipe, DismissAlertStrategy, DynamicComponentAlert } from '@c8y/ngx-components';
import * as i6 from '@angular/forms';
import { NgForm, ControlContainer, FormsModule } from '@angular/forms';
import * as i4$1 from '@c8y/ngx-components/context-dashboard';
import * as i7 from '@c8y/ngx-components/icon-selector';
import { IconSelectorModule } from '@c8y/ngx-components/icon-selector';
import * as i4 from '@c8y/ngx-components/map';
import { defaultFitBoundsOptions, MAP_DEFAULT_CONFIG, MapModule, MapComponent, ClusterMapComponent } from '@c8y/ngx-components/map';
import * as i3 from '@ngx-translate/core';
import * as i8 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import * as i9 from 'rxjs';
import { Subject, fromEvent } from 'rxjs';
import { takeUntil, combineLatestWith, take, filter } from 'rxjs/operators';
import { RouterLink } from '@angular/router';
import { debounce } from 'lodash-es';
class MapEventInfoComponent {
constructor(eventService) {
this.eventService = eventService;
this.loading = true;
}
async ngOnChanges() {
this.loading = true;
const { data } = await this.eventService.list({
dateFrom: '1970-01-01',
dateTo: new Date(Date.now()).toISOString(),
fragmentType: 'c8y_Position',
pageSize: 1,
source: this.asset.id
});
this.event = data[0];
this.loading = false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapEventInfoComponent, deps: [{ token: i1.EventService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: MapEventInfoComponent, isStandalone: true, selector: "c8y-map-event-info", inputs: { asset: "asset" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"text-muted text-10 p-t-4 p-b-4\">\n <span translate>Last position update:</span>\n <br />\n <c8y-loading *ngIf=\"loading\"></c8y-loading>\n <ng-container *ngIf=\"!loading\">\n <i *ngIf=\"!event\" translate>No information found</i>\n <ng-container *ngIf=\"event\">\n <i>{{ event.time | c8yDate }}</i>\n <ng-content></ng-content>\n </ng-container>\n </ng-container>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapEventInfoComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-map-event-info', standalone: true, imports: [CommonModule, C8yTranslateDirective, DatePipe, LoadingComponent], template: "<div class=\"text-muted text-10 p-t-4 p-b-4\">\n <span translate>Last position update:</span>\n <br />\n <c8y-loading *ngIf=\"loading\"></c8y-loading>\n <ng-container *ngIf=\"!loading\">\n <i *ngIf=\"!event\" translate>No information found</i>\n <ng-container *ngIf=\"event\">\n <i>{{ event.time | c8yDate }}</i>\n <ng-content></ng-content>\n </ng-container>\n </ng-container>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1.EventService }], propDecorators: { asset: [{
type: Input
}] } });
class MapWidgetConfigComponent {
set previewMapSet(template) {
if (template) {
this.widgetConfigService.setPreview(template);
return;
}
this.widgetConfigService.setPreview(null);
}
constructor(mapService, alertService, translateService, defaultMapConfig, widgetConfigService) {
this.mapService = mapService;
this.alertService = alertService;
this.translateService = translateService;
this.defaultMapConfig = defaultMapConfig;
this.widgetConfigService = widgetConfigService;
this.config = {
mapConfig: undefined
};
this.canAutoCenter = true;
this.isPositionedDevice = false;
this.refreshOption = 'none';
this.destroyed$ = new Subject();
// JS precision for floating point number is 15 for 64-bit and because of this provided to leaflet are recalculated
// to values almost-like provided (e.g. 0 is recalculated to 1.46e-14; for 180 it's 180.00000000000003).
// To ensure provided value has no floating point error, number needs to be rounded to fixed point 13.
this.FIXED_POINT_DIGITS = 13;
}
ngOnDestroy() {
this.destroyed$.next();
}
ngOnInit() {
this.defaultMapConfig.pipe(takeUntil(this.destroyed$)).subscribe(defaultConfig => {
this.formConfig = {
zoomLevel: defaultConfig.zoomLevel,
center: defaultConfig.center
};
this.initForm();
});
}
async initForm() {
if (!this.config.mapConfig) {
this.config.mapConfig = this.formConfig;
}
this.formConfig = {
...this.formConfig,
...this.config.mapConfig,
disablePan: false
};
await this.updateAsset();
}
async ngOnChanges(changes) {
if (changes.config.currentValue !== changes.config.previousValue &&
!changes.config.firstChange) {
await this.updateAsset();
}
}
previewMapInit(leaflet) {
this.leaflet = leaflet;
this.addCenterIcon(this.formConfig.center[0], this.formConfig.center[1]);
}
onBeforeSave() {
this.config.mapConfig = this.formConfig;
return true;
}
zoomLevelChanged() {
this.config.mapConfig = {
...this.config.mapConfig,
zoomLevel: this.formConfig.zoomLevel
};
}
changeCenter() {
if (this.centerIcon) {
this.centerIcon.remove();
}
this.config.mapConfig = { ...this.formConfig, center: [...this.formConfig.center] };
this.addCenterIcon(this.formConfig.center[0], this.formConfig.center[1]);
}
changeCenterOnEnterKey(event) {
event.preventDefault();
this.changeCenter();
}
onPreviewZoomStart() {
this.centerIcon.remove();
}
onPreviewZoomEnd(event) {
this.formConfig.zoomLevel = Math.floor(event.target.getZoom());
}
onPreviewMapMove(event) {
if (this.centerIcon) {
this.centerIcon.remove();
}
const { lat, lng } = event.target.getCenter();
const fixedLat = +lat.toFixed(this.FIXED_POINT_DIGITS);
const fixedLng = +lng.toFixed(this.FIXED_POINT_DIGITS);
this.addCenterIcon(fixedLat, fixedLng);
this.formConfig.center = [fixedLat, fixedLng];
}
useOwnPosition() {
navigator.geolocation.getCurrentPosition(({ coords }) => {
this.formConfig.center = [coords.latitude, coords.longitude];
this.changeCenter();
});
}
centerToAsset() {
this.formConfig.center = [
this.config.device.c8y_Position.lat,
this.config.device.c8y_Position.lng
];
this.changeCenter();
}
async fitToBounds() {
if (!this.assets?.length || !this.previewMap?.map) {
return;
}
const bounds = await this.mapService.getAssetsBounds(this.assets);
// map.fitBounds causes zoom and move events, so get new center and zoom level only when these events are finished
fromEvent(this.previewMap.map, 'moveend')
.pipe(combineLatestWith(fromEvent(this.previewMap.map, 'zoomend')), take(1))
.subscribe(() => {
const center = this.previewMap.map.getCenter();
const zoom = this.previewMap.map.getZoom();
const fixedLat = +center.lat.toFixed(this.FIXED_POINT_DIGITS);
const fixedLng = +center.lng.toFixed(this.FIXED_POINT_DIGITS);
this.formConfig.center = [fixedLat, fixedLng];
this.formConfig.zoomLevel = Math.floor(zoom);
this.changeCenter();
});
this.previewMap.map.fitBounds(bounds, defaultFitBoundsOptions);
}
updateRefreshOption() {
this.formConfig.realtime = this.refreshOption === 'realtime';
this.config.widgetInstanceGlobalAutoRefreshContext =
this.refreshOption === 'dashboard-auto-refresh-context';
this.formConfig.widgetInstanceGlobalAutoRefreshContext =
this.refreshOption === 'dashboard-auto-refresh-context';
if (this.refreshOption === 'interval') {
this.formConfig.refreshInterval ??= 5000;
}
else {
this.formConfig.refreshInterval = null;
}
if (!this.formConfig.realtime) {
this.formConfig.follow = false;
}
this.config.widgetInstanceGlobalTimeContext =
this.refreshOption === 'dashboard-realtime-context';
if (this.config.widgetInstanceGlobalTimeContext) {
this.formConfig.realtime = null;
}
}
selectIcon(icon) {
this.formConfig.icon = icon;
this.config.mapConfig.icon = icon;
this.previewMap.refreshMarkers();
}
async updateAsset() {
this.canAutoCenter = this.mapService.hasPosition(this.config.device);
this.isPositionedDevice = this.mapService.isPositionedDevice(this.config.device);
this.refreshOption = this.getRefreshOption();
this.updateRefreshOption();
const { data, paging } = await this.mapService.getAllPositionMOs(this.config.device);
this.assets = data;
if (paging.totalPages > 1) {
this.alertService.danger(gettext('It might be possible that assets are not shown in the preview, as the current selection has more than 500 assets and the preview only supports a maximum of 500 assets.'));
}
}
getRefreshOption() {
let option = 'none';
if (this.formConfig.realtime) {
option = 'realtime';
}
else if (this.formConfig.refreshInterval) {
option = 'interval';
}
else if (this.config.widgetInstanceGlobalTimeContext) {
option = 'dashboard-realtime-context';
}
else if (this.config.widgetInstanceGlobalAutoRefreshContext ||
this.formConfig.widgetInstanceGlobalAutoRefreshContext) {
option = 'dashboard-auto-refresh-context';
}
const isNotAllowedOption = (!this.isPositionedDevice && option === 'dashboard-realtime-context') ||
(!this.isPositionedDevice && option === 'realtime') ||
(this.isPositionedDevice && option === 'interval');
option = isNotAllowedOption ? 'none' : option;
return option;
}
addCenterIcon(lat, lng) {
const titleText = this.translateService.instant(gettext('Map center'));
const icon = this.leaflet.divIcon({
html: `<i style="pointer-events: none" class="c8y-map-marker-icon text-muted dlt-c8y-icon-target icon-2x" title="${titleText}" />`
});
this.centerIcon = this.leaflet.marker([lat, lng], {
icon
});
this.previewMap.addMarkerToMap(this.centerIcon);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapWidgetConfigComponent, deps: [{ token: i4.MapService }, { token: i2.AlertService }, { token: i3.TranslateService }, { token: MAP_DEFAULT_CONFIG }, { token: i4$1.WidgetConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: MapWidgetConfigComponent, isStandalone: true, selector: "c8y-map-widget-config", inputs: { config: "config" }, viewQueries: [{ propertyName: "previewMap", first: true, predicate: MapComponent, descendants: true }, { propertyName: "previewMapSet", first: true, predicate: ["previewMap"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formConfig\"\n>\n <legend>{{ 'Map' | translate }}</legend>\n <div class=\"row tight-grid\">\n <div class=\"col-xs-3\">\n <c8y-form-group class=\"m-b-16 text-center form-group-sm\">\n <label translate>Marker icon</label>\n <c8y-icon-selector-wrapper\n [canRemoveIcon]=\"true\"\n [selectedIcon]=\"formConfig.icon\"\n (onSelect)=\"selectIcon($event)\"\n ></c8y-icon-selector-wrapper>\n </c8y-form-group>\n </div>\n <div class=\"col-xs-9\">\n <div class=\"form-group form-group-sm\">\n <label translate>Zoom level</label>\n <c8y-range\n class=\"label-bottom\"\n name=\"zoomLevel\"\n #range\n [(ngModel)]=\"formConfig.zoomLevel\"\n (change)=\"zoomLevelChanged()\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"18\"\n step=\"1\"\n />\n </c8y-range>\n </div>\n </div>\n </div>\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Center bound</label>\n <div class=\"input-group input-group-sm\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'lat.`latitude`' | translate }}\"\n name=\"centerLat\"\n type=\"number\"\n required\n [(ngModel)]=\"formConfig.center[0]\"\n (change)=\"changeCenter()\"\n (keydown.enter)=\"changeCenterOnEnterKey($event)\"\n min=\"-90\"\n max=\"90\"\n step=\"0.1\"\n />\n <input\n class=\"form-control\"\n placeholder=\"{{ 'lng.`longitude`' | translate }}\"\n name=\"centerLng\"\n type=\"number\"\n required\n min=\"-180\"\n max=\"180\"\n [(ngModel)]=\"formConfig.center[1]\"\n (change)=\"changeCenter()\"\n (keydown.enter)=\"changeCenterOnEnterKey($event)\"\n min=\"-180\"\n max=\"180\"\n step=\"0.1\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Use your location' | translate\"\n [tooltip]=\"'Use your location' | translate\"\n placement=\"top\"\n container=\"body\"\n (click)=\"useOwnPosition()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"location-arrow\"\n ></i>\n </button>\n </div>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Use selected asset location' | translate\"\n [tooltip]=\"'Use selected asset location' | translate\"\n placement=\"top\"\n container=\"body\"\n *ngIf=\"canAutoCenter; else fitToBoundsButton\"\n (click)=\"centerToAsset()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"location\"\n ></i>\n </button>\n </div>\n </div>\n\n <ng-template #fitToBoundsButton>\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Fit to assets bounds' | translate\"\n [tooltip]=\"'Fit to assets bounds' | translate\"\n placement=\"top\"\n container=\"body\"\n (click)=\"fitToBounds()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"waypoint-map\"\n ></i>\n </button>\n </ng-template>\n <c8y-messages [helpMessage]=\"'Drag the map to the desired position' | translate\"></c8y-messages>\n </c8y-form-group>\n</fieldset>\n\n<fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formConfig\"\n>\n <legend>{{ 'Auto refresh' | translate }}</legend>\n <div class=\"row tight-grid d-flex a-i-center\">\n <div class=\"col-xs-6\">\n <c8y-form-group class=\"m-b-16 form-group-sm\">\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n title=\"{{ 'Refresh options`options for refreshing a view`' | translate }}\"\n id=\"selectExample\"\n name=\"refreshSelection\"\n [(ngModel)]=\"refreshOption\"\n (change)=\"updateRefreshOption()\"\n >\n <option\n [title]=\"'Only refreshing on interaction' | translate\"\n value=\"none\"\n translate\n >\n No automatic refresh\n </option>\n <option\n [title]=\"'Refreshing after the given interval and on interaction' | translate\"\n value=\"interval\"\n *ngIf=\"!isPositionedDevice\"\n translate\n >\n Use refresh interval\n </option>\n <option\n [title]=\"\n 'Refreshing after the given interval and on interaction, synchronized globally'\n | translate\n \"\n value=\"dashboard-auto-refresh-context\"\n *ngIf=\"!isPositionedDevice\"\n translate\n >\n Use global refresh interval\n </option>\n <option\n [title]=\"'Live updating on each position change' | translate\"\n value=\"realtime\"\n *ngIf=\"isPositionedDevice\"\n translate\n >\n Realtime\n </option>\n <option\n [title]=\"'Bind widget to dashboard realtime context' | translate\"\n value=\"dashboard-realtime-context\"\n *ngIf=\"isPositionedDevice\"\n translate\n >\n Dashboard realtime context\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-xs-6\">\n <div class=\"form-group form-group-sm m-b-16\">\n <c8y-range\n class=\"label-bottom\"\n name=\"refreshInterval\"\n #intervalRange\n *ngIf=\"refreshOption === 'interval'\"\n [(ngModel)]=\"formConfig.refreshInterval\"\n >\n <ng-template #c8yRangeValue>\n <div>\n <span\n [translateParams]=\"{ intervalInSeconds: intervalRange.value * 0.001 }\"\n translate\n ngNonBindable\n >\n {{ intervalInSeconds }}s\n </span>\n </div>\n </ng-template>\n <input\n type=\"range\"\n min=\"5000\"\n max=\"100000\"\n step=\"1000\"\n />\n </c8y-range>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline\"\n *ngIf=\"refreshOption === 'realtime' || refreshOption === 'dashboard-realtime-context'\"\n >\n <input\n name=\"follow\"\n type=\"checkbox\"\n [(ngModel)]=\"formConfig.follow\"\n />\n <span></span>\n <span\n class=\"text-12\"\n translate\n >\n Follow selected\n </span>\n </label>\n </div>\n </div>\n</fieldset>\n\n<ng-template #previewMap>\n <div style=\"width: 100%; height: 100%\">\n <c8y-map\n *ngIf=\"config.mapConfig\"\n [assets]=\"assets\"\n [config]=\"config.mapConfig\"\n (onMove)=\"onPreviewMapMove($event)\"\n (onZoomStart)=\"onPreviewZoomStart()\"\n (onZoomEnd)=\"onPreviewZoomEnd($event)\"\n (onInit)=\"previewMapInit($event)\"\n ></c8y-map>\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i6.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i6.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i6.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i6.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i6.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i6.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i6.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule$1 }, { kind: "directive", type: i2.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: i2.C8yTranslatePipe, name: "translate" }, { kind: "directive", type: i2.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i2.MinValidationDirective, selector: "[min]", inputs: ["min"] }, { kind: "directive", type: i2.MaxValidationDirective, selector: "[max]", inputs: ["max"] }, { kind: "component", type: i2.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: i2.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: i2.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: i2.RangeDirective, selector: "input[type=\"range\"]" }, { kind: "component", type: i2.RangeComponent, selector: "c8y-range", inputs: ["valueDisplayMode"] }, { kind: "ngmodule", type: IconSelectorModule }, { kind: "component", type: i7.IconSelectorWrapperComponent, selector: "c8y-icon-selector-wrapper", inputs: ["canRemoveIcon", "selectedIcon", "iconSize"], outputs: ["onSelect"] }, { kind: "ngmodule", type: MapModule }, { kind: "component", type: i4.MapComponent, selector: "c8y-map", inputs: ["config", "assets", "polyline$", "polylineOptions"], outputs: ["onRealtimeUpdate", "onMove", "onMoveEnd", "onZoomStart", "onZoomEnd", "onMap", "onInit"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i8.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapWidgetConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-map-widget-config', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
CommonModule,
FormsModule,
CommonModule$1,
FormsModule$1,
IconSelectorModule,
MapModule,
AssetLinkPipe,
TooltipModule
], template: "<fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formConfig\"\n>\n <legend>{{ 'Map' | translate }}</legend>\n <div class=\"row tight-grid\">\n <div class=\"col-xs-3\">\n <c8y-form-group class=\"m-b-16 text-center form-group-sm\">\n <label translate>Marker icon</label>\n <c8y-icon-selector-wrapper\n [canRemoveIcon]=\"true\"\n [selectedIcon]=\"formConfig.icon\"\n (onSelect)=\"selectIcon($event)\"\n ></c8y-icon-selector-wrapper>\n </c8y-form-group>\n </div>\n <div class=\"col-xs-9\">\n <div class=\"form-group form-group-sm\">\n <label translate>Zoom level</label>\n <c8y-range\n class=\"label-bottom\"\n name=\"zoomLevel\"\n #range\n [(ngModel)]=\"formConfig.zoomLevel\"\n (change)=\"zoomLevelChanged()\"\n >\n <input\n type=\"range\"\n min=\"0\"\n max=\"18\"\n step=\"1\"\n />\n </c8y-range>\n </div>\n </div>\n </div>\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Center bound</label>\n <div class=\"input-group input-group-sm\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'lat.`latitude`' | translate }}\"\n name=\"centerLat\"\n type=\"number\"\n required\n [(ngModel)]=\"formConfig.center[0]\"\n (change)=\"changeCenter()\"\n (keydown.enter)=\"changeCenterOnEnterKey($event)\"\n min=\"-90\"\n max=\"90\"\n step=\"0.1\"\n />\n <input\n class=\"form-control\"\n placeholder=\"{{ 'lng.`longitude`' | translate }}\"\n name=\"centerLng\"\n type=\"number\"\n required\n min=\"-180\"\n max=\"180\"\n [(ngModel)]=\"formConfig.center[1]\"\n (change)=\"changeCenter()\"\n (keydown.enter)=\"changeCenterOnEnterKey($event)\"\n min=\"-180\"\n max=\"180\"\n step=\"0.1\"\n />\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Use your location' | translate\"\n [tooltip]=\"'Use your location' | translate\"\n placement=\"top\"\n container=\"body\"\n (click)=\"useOwnPosition()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"location-arrow\"\n ></i>\n </button>\n </div>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Use selected asset location' | translate\"\n [tooltip]=\"'Use selected asset location' | translate\"\n placement=\"top\"\n container=\"body\"\n *ngIf=\"canAutoCenter; else fitToBoundsButton\"\n (click)=\"centerToAsset()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"location\"\n ></i>\n </button>\n </div>\n </div>\n\n <ng-template #fitToBoundsButton>\n <button\n class=\"btn btn-default\"\n [attr.aria-label]=\"'Fit to assets bounds' | translate\"\n [tooltip]=\"'Fit to assets bounds' | translate\"\n placement=\"top\"\n container=\"body\"\n (click)=\"fitToBounds()\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"waypoint-map\"\n ></i>\n </button>\n </ng-template>\n <c8y-messages [helpMessage]=\"'Drag the map to the desired position' | translate\"></c8y-messages>\n </c8y-form-group>\n</fieldset>\n\n<fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formConfig\"\n>\n <legend>{{ 'Auto refresh' | translate }}</legend>\n <div class=\"row tight-grid d-flex a-i-center\">\n <div class=\"col-xs-6\">\n <c8y-form-group class=\"m-b-16 form-group-sm\">\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n title=\"{{ 'Refresh options`options for refreshing a view`' | translate }}\"\n id=\"selectExample\"\n name=\"refreshSelection\"\n [(ngModel)]=\"refreshOption\"\n (change)=\"updateRefreshOption()\"\n >\n <option\n [title]=\"'Only refreshing on interaction' | translate\"\n value=\"none\"\n translate\n >\n No automatic refresh\n </option>\n <option\n [title]=\"'Refreshing after the given interval and on interaction' | translate\"\n value=\"interval\"\n *ngIf=\"!isPositionedDevice\"\n translate\n >\n Use refresh interval\n </option>\n <option\n [title]=\"\n 'Refreshing after the given interval and on interaction, synchronized globally'\n | translate\n \"\n value=\"dashboard-auto-refresh-context\"\n *ngIf=\"!isPositionedDevice\"\n translate\n >\n Use global refresh interval\n </option>\n <option\n [title]=\"'Live updating on each position change' | translate\"\n value=\"realtime\"\n *ngIf=\"isPositionedDevice\"\n translate\n >\n Realtime\n </option>\n <option\n [title]=\"'Bind widget to dashboard realtime context' | translate\"\n value=\"dashboard-realtime-context\"\n *ngIf=\"isPositionedDevice\"\n translate\n >\n Dashboard realtime context\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-xs-6\">\n <div class=\"form-group form-group-sm m-b-16\">\n <c8y-range\n class=\"label-bottom\"\n name=\"refreshInterval\"\n #intervalRange\n *ngIf=\"refreshOption === 'interval'\"\n [(ngModel)]=\"formConfig.refreshInterval\"\n >\n <ng-template #c8yRangeValue>\n <div>\n <span\n [translateParams]=\"{ intervalInSeconds: intervalRange.value * 0.001 }\"\n translate\n ngNonBindable\n >\n {{ intervalInSeconds }}s\n </span>\n </div>\n </ng-template>\n <input\n type=\"range\"\n min=\"5000\"\n max=\"100000\"\n step=\"1000\"\n />\n </c8y-range>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline\"\n *ngIf=\"refreshOption === 'realtime' || refreshOption === 'dashboard-realtime-context'\"\n >\n <input\n name=\"follow\"\n type=\"checkbox\"\n [(ngModel)]=\"formConfig.follow\"\n />\n <span></span>\n <span\n class=\"text-12\"\n translate\n >\n Follow selected\n </span>\n </label>\n </div>\n </div>\n</fieldset>\n\n<ng-template #previewMap>\n <div style=\"width: 100%; height: 100%\">\n <c8y-map\n *ngIf=\"config.mapConfig\"\n [assets]=\"assets\"\n [config]=\"config.mapConfig\"\n (onMove)=\"onPreviewMapMove($event)\"\n (onZoomStart)=\"onPreviewZoomStart()\"\n (onZoomEnd)=\"onPreviewZoomEnd($event)\"\n (onInit)=\"previewMapInit($event)\"\n ></c8y-map>\n </div>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i4.MapService }, { type: i2.AlertService }, { type: i3.TranslateService }, { type: i9.Observable, decorators: [{
type: Inject,
args: [MAP_DEFAULT_CONFIG]
}] }, { type: i4$1.WidgetConfigService }], propDecorators: { config: [{
type: Input
}], previewMap: [{
type: ViewChild,
args: [MapComponent]
}], previewMapSet: [{
type: ViewChild,
args: ['previewMap']
}] } });
class MapWidgetComponent {
constructor(dashboardChild, dashboardContextComponent, inventory, mapService, widgetsDashboardComponent) {
this.dashboardContextComponent = dashboardContextComponent;
this.inventory = inventory;
this.mapService = mapService;
this.widgetsDashboardComponent = widgetsDashboardComponent;
this.mapConfig = { center: [0, 0] };
this.TIMEOUT_ERROR_TEXT = gettext('The request is taking longer than usual. We apologize for the inconvenience.');
this.SERVER_ERROR_TEXT = gettext('Server error occurred.');
this.destroy$ = new Subject();
this.listenToWidgetResizeEvent(dashboardChild);
}
async ngOnInit() {
this.alerts.setAlertGroupDismissStrategy('warning', DismissAlertStrategy.TEMPORARY_OR_PERMANENT);
if (this.dashboardContextComponent?.dashboard?.deviceType && !this.config.device) {
const context = this.dashboardContextComponent.context;
if (context?.id) {
const { id } = context;
this.config.device = (await this.inventory.detail(id)).data;
}
}
if (this.config.device) {
this.rootNode = this.config.device;
}
this.mapConfig = { ...this.config.mapConfig };
if (this.config.widgetInstanceGlobalTimeContext) {
this.updateMapConfigRealtime();
}
this.savedNode = this.rootNode;
await this.updateAssets();
}
ngOnChanges(changes) {
if (changes.config?.currentValue?.widgetInstanceGlobalTimeContext) {
this.updateMapConfigRealtime();
}
}
ngAfterViewInit() {
this.subscribeToErrorsOccurred();
if (this.widgetsDashboardComponent) {
const resizeCallback = debounce(() => {
// initial size of map is assigned when left drawer is closed, but if it is opened after initialization
// the map size is not updated, so we need to call invalidateSize so that map use the correct size
this.clusterMap.map?.invalidateSize(false);
}, 100);
this.resizeObserver = new ResizeObserver(resizeCallback);
this.resizeObserver.observe(this.widgetsDashboardComponent.nativeElement);
}
}
startFollow(context) {
if (context.id !== this.rootNode?.id) {
this.rootNode = context;
}
this.mapConfig = {
...this.config.mapConfig,
follow: true,
realtime: true
};
}
stopFollow() {
this.mapConfig = {
...this.config.mapConfig,
follow: false
};
if (this.config.widgetInstanceGlobalTimeContext) {
this.updateMapConfigRealtime();
}
this.rootNode = this.savedNode;
}
ngOnDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
this.destroy$.next();
}
async fitToBounds() {
await this.updateAssets();
if (this.assets.length === 0 || !this.clusterMap?.map) {
return;
}
const bounds = await this.mapService.getAssetsBounds(this.assets);
this.clusterMap.map.fitBounds(bounds, defaultFitBoundsOptions);
}
async updateAssets() {
this.assets = (await this.mapService.getAllPositionMOs(this.config.device)).data;
}
listenToWidgetResizeEvent(dashboardChild) {
dashboardChild.changeEnd
.pipe(filter(child => child.lastChange === 'resize'), takeUntil(this.destroy$))
.subscribe(() => {
this.clusterMap.reset();
});
}
subscribeToErrorsOccurred() {
this.clusterMap.errorNotifier
.pipe(filter(Boolean), takeUntil(this.destroy$))
.subscribe(error => {
this.alerts.addAlerts(new DynamicComponentAlert({
type: 'warning',
text: error?.name === 'TimeoutError'
? this.TIMEOUT_ERROR_TEXT
: (error?.message ?? this.SERVER_ERROR_TEXT)
}));
});
}
updateMapConfigRealtime() {
this.mapConfig.realtime = this.config.realtime || false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapWidgetComponent, deps: [{ token: i2.DashboardChildComponent }, { token: i4$1.ContextDashboardComponent, optional: true }, { token: i1.InventoryService }, { token: i4.MapService }, { token: i2.WidgetsDashboardComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: MapWidgetComponent, isStandalone: true, selector: "c8y-map-widget", inputs: { config: "config" }, viewQueries: [{ propertyName: "clusterMap", first: true, predicate: ClusterMapComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<c8y-map-status\n [clusterMap]=\"mapWidget\"\n [(config)]=\"mapConfig\"\n (onUnfollow)=\"stopFollow()\"\n (fitAssetsToBounds)=\"fitToBounds()\"\n [buttonsConfig]=\"\n config.widgetInstanceGlobalTimeContext\n ? { realtime: { show: false }, fitToBounds: { show: true } }\n : { fitToBounds: { show: true } }\n \"\n [assets]=\"assets\"\n></c8y-map-status>\n<c8y-cluster-map\n #mapWidget\n [rootNode]=\"rootNode\"\n [config]=\"mapConfig\"\n>\n <div\n class=\"map-marker\"\n *c8yMapPopup=\"let context\"\n >\n <a\n class=\"text-truncate deviceLink text-12\"\n routerLink=\"{{ context | assetLink }}\"\n >\n <strong>{{ context.name }}</strong>\n </a>\n <c8y-map-event-info [asset]=\"context\">\n <button\n class=\"btn btn-default btn-xs btn-block m-t-8\"\n [title]=\"'Activate realtime on this asset and follow it if it moves' | translate\"\n type=\"button\"\n (click)=\"startFollow(context)\"\n *ngIf=\"!mapConfig.follow\"\n translate\n >\n Follow\n </button>\n <button\n class=\"btn btn-default btn-xs btn-block m-t-8\"\n [title]=\"'Stop following this asset.' | translate\"\n type=\"button\"\n (click)=\"stopFollow()\"\n *ngIf=\"mapConfig.follow\"\n translate\n >\n Unfollow\n </button>\n </c8y-map-event-info>\n </div>\n</c8y-cluster-map>\n", dependencies: [{ kind: "component", type: MapEventInfoComponent, selector: "c8y-map-event-info", inputs: ["asset"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: CommonModule$1 }, { kind: "pipe", type: i2.C8yTranslatePipe, name: "translate" }, { kind: "directive", type: i2.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: i2.AssetLinkPipe, name: "assetLink" }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "ngmodule", type: IconSelectorModule }, { kind: "ngmodule", type: MapModule }, { kind: "component", type: i4.MapStatusComponent, selector: "c8y-map-status", inputs: ["config", "assets", "clusterMap", "buttonsConfig"], outputs: ["configChange", "onUnfollow", "fitAssetsToBounds"] }, { kind: "component", type: i4.ClusterMapComponent, selector: "c8y-cluster-map", inputs: ["config", "rootNode", "asset", "showClusterColor"], outputs: ["mapChange"] }, { kind: "directive", type: i4.MapPopupDirective, selector: "[c8yMapPopup]" }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MapWidgetComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-map-widget', standalone: true, imports: [
MapEventInfoComponent,
CommonModule,
FormsModule,
CommonModule$1,
FormsModule$1,
IconSelectorModule,
MapModule,
AssetLinkPipe,
TooltipModule,
RouterLink
], template: "<c8y-map-status\n [clusterMap]=\"mapWidget\"\n [(config)]=\"mapConfig\"\n (onUnfollow)=\"stopFollow()\"\n (fitAssetsToBounds)=\"fitToBounds()\"\n [buttonsConfig]=\"\n config.widgetInstanceGlobalTimeContext\n ? { realtime: { show: false }, fitToBounds: { show: true } }\n : { fitToBounds: { show: true } }\n \"\n [assets]=\"assets\"\n></c8y-map-status>\n<c8y-cluster-map\n #mapWidget\n [rootNode]=\"rootNode\"\n [config]=\"mapConfig\"\n>\n <div\n class=\"map-marker\"\n *c8yMapPopup=\"let context\"\n >\n <a\n class=\"text-truncate deviceLink text-12\"\n routerLink=\"{{ context | assetLink }}\"\n >\n <strong>{{ context.name }}</strong>\n </a>\n <c8y-map-event-info [asset]=\"context\">\n <button\n class=\"btn btn-default btn-xs btn-block m-t-8\"\n [title]=\"'Activate realtime on this asset and follow it if it moves' | translate\"\n type=\"button\"\n (click)=\"startFollow(context)\"\n *ngIf=\"!mapConfig.follow\"\n translate\n >\n Follow\n </button>\n <button\n class=\"btn btn-default btn-xs btn-block m-t-8\"\n [title]=\"'Stop following this asset.' | translate\"\n type=\"button\"\n (click)=\"stopFollow()\"\n *ngIf=\"mapConfig.follow\"\n translate\n >\n Unfollow\n </button>\n </c8y-map-event-info>\n </div>\n</c8y-cluster-map>\n" }]
}], ctorParameters: () => [{ type: i2.DashboardChildComponent }, { type: i4$1.ContextDashboardComponent, decorators: [{
type: Optional
}] }, { type: i1.InventoryService }, { type: i4.MapService }, { type: i2.WidgetsDashboardComponent, decorators: [{
type: Optional
}] }], propDecorators: { config: [{
type: Input
}], clusterMap: [{
type: ViewChild,
args: [ClusterMapComponent]
}] } });
/**
* Generated bundle index. Do not edit.
*/
export { MapEventInfoComponent, MapWidgetComponent, MapWidgetConfigComponent };
//# sourceMappingURL=c8y-ngx-components-widgets-implementations-map.mjs.map