UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

416 lines (410 loc) 44.2 kB
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