UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

242 lines 62.6 kB
import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, Output, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { InventoryService } from '@c8y/client'; import { gettext } from '../i18n/gettext'; import { BsDropdownDirective } from 'ngx-bootstrap/dropdown'; import { defer, empty, merge, of, Subject } from 'rxjs'; import { switchMap, takeUntil, tap } from 'rxjs/operators'; import { ManagedObjectType } from '../common/managed-object-type'; import { TypeaheadComponent } from '../select/typeahead.component'; import { InventorySearchService } from './inventory-search.service'; import * as i0 from "@angular/core"; import * as i1 from "@angular/router"; import * as i2 from "@c8y/client"; import * as i3 from "./inventory-search.service"; import * as i4 from "@angular/cdk/a11y"; import * as i5 from "@angular/forms"; import * as i6 from "ngx-bootstrap/dropdown"; import * as i7 from "../common/empty-state/empty-state.component"; import * as i8 from "../common/icon.directive"; import * as i9 from "../i18n/c8y-translate.directive"; import * as i10 from "@angular/common"; import * as i11 from "../common/forOf.directive"; import * as i12 from "../common/loading.component"; import * as i13 from "../select/typeahead.component"; import * as i14 from "../list-group/list-item.component"; import * as i15 from "../list-group/list-item-icon.component"; import * as i16 from "../device-status/device-status.component"; import * as i17 from "../i18n/c8y-translate.pipe"; import * as i18 from "../common/should-show-mo.pipe"; import * as i19 from "../common/get-group-icon.pipe"; export class SearchInputComponent { /** * A custom query setter used to override the standard query. In order to obtain data. */ set customDataQuery(query) { if (query) { this.customQuery = query; } } constructor(router, inventory, inventorySearchService, cd) { this.router = router; this.inventory = inventory; this.inventorySearchService = inventorySearchService; this.cd = cd; this.mode = 'search'; /** * Unlocks the ability to place a custom template under the search input. */ this.enableCustomTemplatePlaceholder = false; this.container = ''; this.groupsOnly = false; this.filter = new EventEmitter(); this.search = new EventEmitter(); this.reset = new EventEmitter(); this.onClick = new EventEmitter(); this.deviceType = ManagedObjectType; this.term = ''; this.defaultPlaceholder = gettext('Search for groups or assets…'); this.recentSearchResults = []; this.isLoading = false; this.noMatch = false; this.RECENT_SEARCH_STORAGE_KEY = 'recent_search_view'; this.MAX_RECENT_SEARCH_RESULTS = 5; this.DEFAULT_FILTER = { withTotalPages: true, pageSize: 5, withChildren: false }; this.KEYCODE_ENTER = 'Enter'; this.KEYCODE_ESC = 'Escape'; this.onDestroy$ = new Subject(); } async ngOnInit() { const recentSearchIds = JSON.parse(localStorage.getItem(this.RECENT_SEARCH_STORAGE_KEY)); if (recentSearchIds && recentSearchIds.length > 0) { const { data } = await this.inventory.list({ ids: recentSearchIds.join(',') }); this.recentSearchResults = this.groupsOnly ? this.inventorySearchService.filterOnlyGroups(data) : data; this.recentlyRegisteredResults$ = defer(() => this.inventory.list({ q: '$orderby=creationTime desc', ...this.DEFAULT_FILTER })); } if (this.mode === 'select') { requestAnimationFrame(() => { this.subscribeOnSearch(); }); } if (this.externalTerm) { this.externalTerm.pipe(takeUntil(this.onDestroy$)).subscribe(term => { if (term === null) { this.typeahead.onSearch.emit(this.term); return; } this.typeahead.onSearch.emit(term); }); } } onKeydownHandler(event) { if (event.key === this.KEYCODE_ESC) { this.hideDropdown(); } } onOpenChange(isOpen) { if (isOpen) { // needs to request an animation frame as // otherwise the typeahead is undefined requestAnimationFrame(() => { this.subscribeOnSearch(); this.typeahead.dropdown.show(); this.typeahead.searchControl.nativeElement.focus(); }); } } open(event, mo, term) { event.stopPropagation(); const isAlreadyRecent = this.recentSearchResults.find(({ id }) => id === mo.id); if (!isAlreadyRecent) { this.recentSearchResults.unshift(mo); this.recentSearchResults = this.recentSearchResults.slice(0, this.MAX_RECENT_SEARCH_RESULTS); } const recentSearchIds = this.recentSearchResults.map(({ id }) => id); localStorage.setItem(this.RECENT_SEARCH_STORAGE_KEY, JSON.stringify(recentSearchIds)); if (term) { this.selected = mo; this.term = term; } this.onClick.emit(mo); this.hideDropdown(); } onReset(status) { status.$event.stopPropagation(); this.reset.emit(this.selected); this.typeahead.onSearch.emit(''); this.selected = undefined; this.typeahead.searchControl.nativeElement.focus(); } keyDown(event) { if (event.key === this.KEYCODE_ENTER) { // enter hit can be faster then typeahead debounce, // therefore we take the term from the DOM element // itself: const searchTerm = event.target.value; this.onSearch(searchTerm); } } onSearch(search) { this.search.emit(search); this.hideDropdown(); } onFilter(search) { this.filter.emit(search); this.hideDropdown(); } onOpenAssetTable() { this.router.navigateByUrl('/assetsearch'); this.hideDropdown(); } ngOnDestroy() { if (this.onDestroy$) { this.onDestroy$.next(); this.onDestroy$.complete(); } } hideDropdown() { if (this.dropdown) { this.dropdown.hide(); return; } if (this.typeahead && this.typeahead.dropdown) { this.typeahead.dropdown.hide(); return; } } subscribeOnSearch() { if (!this.results$) { this.results$ = this.typeahead.onSearch.pipe(tap(term => this.onTypingStarted(term)), switchMap(term => this.mergeRequest(term)), tap(() => this.cd.detectChanges())); } } mergeRequest(term) { return merge(of({ data: [] }), this.handleQuery(term).pipe(tap(({ data, paging }) => this.onLoadingDone(data, paging)))); } handleQuery(term) { if (!term) { return empty(); } if (this.customQuery) { return defer(() => this.customQuery(term)); } return defer(() => this.groupsOnly ? this.inventorySearchService.searchGroups(term) : this.inventorySearchService.search(term)); } onLoadingDone(data, paging) { this.isLoading = false; this.noMatch = paging && paging.nextPage === null && data.length === 0; } onTypingStarted(term) { this.noMatch = false; this.term = term; this.isLoading = term.length > 0; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SearchInputComponent, deps: [{ token: i1.Router }, { token: i2.InventoryService }, { token: i3.InventorySearchService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: SearchInputComponent, selector: "c8y-search-input", inputs: { mode: "mode", enableCustomTemplatePlaceholder: "enableCustomTemplatePlaceholder", customPlaceholder: "customPlaceholder", externalTerm: "externalTerm", customDataQuery: "customDataQuery", container: "container", groupsOnly: "groupsOnly" }, outputs: { filter: "filter", search: "search", reset: "reset", onClick: "onClick" }, host: { listeners: { "document:keydown": "onKeydownHandler($event)" } }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: TypeaheadComponent, descendants: true }, { propertyName: "dropdown", first: true, predicate: ["searchDropdown"], descendants: true }], ngImport: i0, template: "<div\n class=\"dropdown\"\n #searchDropdown=\"bs-dropdown\"\n [insideClick]=\"true\"\n (isOpenChange)=\"onOpenChange($event)\"\n [cdkTrapFocus]=\"searchDropdown.isOpen\"\n *ngIf=\"mode === 'search'\"\n dropdown\n>\n <button\n class=\"main-header-button dropdown-toggle c8y-dropdown\"\n [title]=\"'Search' | translate\"\n type=\"button\"\n dropdownToggle\n data-cy=\"search-input--search-btn\"\n >\n <i\n class=\"icon-2x\"\n c8yIcon=\"search\"\n ></i>\n </button>\n\n <div\n class=\"search-header-menu dropdown-menu dropdown-menu-right\"\n id=\"searchDropdown\"\n *dropdownMenu\n >\n <ng-container *ngTemplateOutlet=\"form\"></ng-container>\n </div>\n</div>\n\n<div\n class=\"search-header-inline\"\n *ngIf=\"mode === 'select'\"\n>\n <ng-container *ngTemplateOutlet=\"form\"></ng-container>\n</div>\n<ng-template #form>\n <form\n [ngClass]=\"{ 'c8y-search-form': mode === 'search' }\"\n novalidate\n #searchForm=\"ngForm\"\n >\n <c8y-typeahead\n (onIconClick)=\"onReset($event)\"\n [icon]=\"term ? 'times' : 'search'\"\n title=\"Search\"\n placeholder=\"{{ customPlaceholder ? customPlaceholder : defaultPlaceholder }}\"\n name=\"selected\"\n [(ngModel)]=\"selected\"\n (keydown)=\"keyDown($event)\"\n [allowFreeEntries]=\"false\"\n [container]=\"container\"\n [highlightFirstItem]=\"false\"\n >\n <div\n class=\"c8y-list__item p-b-8 separator-bottom sticky-top p-t-4\"\n *ngIf=\"enableCustomTemplatePlaceholder && mode === 'search'\"\n >\n <ng-content></ng-content>\n </div>\n\n <!-- filter buttons -->\n <c8y-li\n *ngIf=\"term.length !== 0 && mode === 'search'\"\n [selectable]=\"false\"\n >\n <div class=\"d-flex\">\n <p class=\"m-r-4 text-muted\">\n <em translate>Searching by exact match. Click for other search options:</em>\n </p>\n <div class=\"btn-group btn-group-sm\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Starts with' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-starts-with\"\n (click)=\"onFilter(term + '*')\"\n >\n {{ 'Starts with' | translate }}\n </button>\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Contains' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-contains\"\n (click)=\"onFilter('*' + term + '*')\"\n >\n {{ 'Contains' | translate }}\n </button>\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Ends with' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-ends-with\"\n (click)=\"onFilter('*' + term)\"\n >\n {{ 'Ends with' | translate }}\n </button>\n </div>\n </div>\n </c8y-li>\n\n <!-- Recent search -->\n <c8y-li\n *ngIf=\"term.length === 0 && recentSearchResults.length > 0\"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block\">\n <span translate>Recent search views</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n *ngFor=\"let result of term.length === 0 ? recentSearchResults : []\"\n (click)=\"open($event, result, result.name)\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- Recently registered devices -->\n <c8y-li\n *ngIf=\"\n term.length === 0 && (recentlyRegisteredResults$ | async)?.data?.length > 0 && !groupsOnly\n \"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block\">\n <span translate>Recently registered devices</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n *c8yFor=\"\n let result of term.length === 0 && !groupsOnly\n ? recentlyRegisteredResults$\n : { data: [] };\n loadMore: 'none'\n \"\n (click)=\"open($event, result, result.name)\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- Search results -->\n <c8y-li\n *ngIf=\"term.length !== 0\"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block m-0\">\n <span translate>Search results</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n [title]=\"result.name\"\n *c8yFor=\"\n let result of results$;\n loadMore: 'auto';\n notFound: notFoundTemplate;\n loadingTemplate: loading;\n loadNextLabel: 'Find more\u2026'\n \"\n (click)=\"open($event, result, result.name)\"\n data-cy=\"search-input--search-results\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- No search results found entry -->\n <ng-template #notFoundTemplate>\n <c8y-ui-empty-state\n [icon]=\"'search'\"\n [title]=\"'No match found.' | translate\"\n data-cy=\"search-input--empty-state\"\n [ngClass]=\"{ 'p-4': mode === 'search' }\"\n [horizontal]=\"true\"\n *ngIf=\"noMatch\"\n >\n <small\n translate\n *ngIf=\"mode === 'search'\"\n >\n Try to filter or open the asset grid to show all devices and groups.\n </small>\n <small\n translate\n *ngIf=\"mode === 'select'\"\n >\n Try to rephrase your search word.\n </small>\n </c8y-ui-empty-state>\n </ng-template>\n\n <!-- loading bar first entries -->\n <c8y-li *ngIf=\"isLoading\">\n <c8y-loading></c8y-loading>\n </c8y-li>\n\n <!-- loading bar for loading more entries (inventory roles) -->\n <ng-template #loading>\n <c8y-li>\n <c8y-loading></c8y-loading>\n </c8y-li>\n </ng-template>\n\n <!-- more filter possibilities -->\n <c8y-li\n class=\"m-t-24 bg-level-2 p-t-16 p-b-16 p-l-24 p-r-24 sticky-bottom\"\n [selectable]=\"false\"\n *ngIf=\"mode === 'search'\"\n >\n <div class=\"d-flex a-i-center\">\n <i\n class=\"text-info m-r-4\"\n c8yIcon=\"info-circle\"\n ></i>\n <p\n class=\"m-r-8\"\n translate\n >\n Need more filter possibilities?\n </p>\n <button\n class=\"m-l-16 btn btn-default btn-sm\"\n title=\"{{ 'Go to the asset data table' | translate }}\"\n type=\"button\"\n (mousedown)=\"onOpenAssetTable()\"\n data-cy=\"search-input--asset-table-btn\"\n >\n {{ 'Go to the asset data table' | translate }}\n </button>\n </div>\n </c8y-li>\n </c8y-typeahead>\n </form>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i5.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i6.BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: i6.BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: i6.BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "component", type: i7.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i8.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i9.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i10.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i10.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i10.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i10.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i11.ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: i12.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: i13.TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "component", type: i14.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i15.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i16.DeviceStatusComponent, selector: "device-status, c8y-device-status", inputs: ["mo", "size"] }, { kind: "pipe", type: i17.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i10.AsyncPipe, name: "async" }, { kind: "pipe", type: i18.ShouldShowMoPipe, name: "shouldShowMo" }, { kind: "pipe", type: i19.GetGroupIconPipe, name: "getGroupIcon" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SearchInputComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-search-input', template: "<div\n class=\"dropdown\"\n #searchDropdown=\"bs-dropdown\"\n [insideClick]=\"true\"\n (isOpenChange)=\"onOpenChange($event)\"\n [cdkTrapFocus]=\"searchDropdown.isOpen\"\n *ngIf=\"mode === 'search'\"\n dropdown\n>\n <button\n class=\"main-header-button dropdown-toggle c8y-dropdown\"\n [title]=\"'Search' | translate\"\n type=\"button\"\n dropdownToggle\n data-cy=\"search-input--search-btn\"\n >\n <i\n class=\"icon-2x\"\n c8yIcon=\"search\"\n ></i>\n </button>\n\n <div\n class=\"search-header-menu dropdown-menu dropdown-menu-right\"\n id=\"searchDropdown\"\n *dropdownMenu\n >\n <ng-container *ngTemplateOutlet=\"form\"></ng-container>\n </div>\n</div>\n\n<div\n class=\"search-header-inline\"\n *ngIf=\"mode === 'select'\"\n>\n <ng-container *ngTemplateOutlet=\"form\"></ng-container>\n</div>\n<ng-template #form>\n <form\n [ngClass]=\"{ 'c8y-search-form': mode === 'search' }\"\n novalidate\n #searchForm=\"ngForm\"\n >\n <c8y-typeahead\n (onIconClick)=\"onReset($event)\"\n [icon]=\"term ? 'times' : 'search'\"\n title=\"Search\"\n placeholder=\"{{ customPlaceholder ? customPlaceholder : defaultPlaceholder }}\"\n name=\"selected\"\n [(ngModel)]=\"selected\"\n (keydown)=\"keyDown($event)\"\n [allowFreeEntries]=\"false\"\n [container]=\"container\"\n [highlightFirstItem]=\"false\"\n >\n <div\n class=\"c8y-list__item p-b-8 separator-bottom sticky-top p-t-4\"\n *ngIf=\"enableCustomTemplatePlaceholder && mode === 'search'\"\n >\n <ng-content></ng-content>\n </div>\n\n <!-- filter buttons -->\n <c8y-li\n *ngIf=\"term.length !== 0 && mode === 'search'\"\n [selectable]=\"false\"\n >\n <div class=\"d-flex\">\n <p class=\"m-r-4 text-muted\">\n <em translate>Searching by exact match. Click for other search options:</em>\n </p>\n <div class=\"btn-group btn-group-sm\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Starts with' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-starts-with\"\n (click)=\"onFilter(term + '*')\"\n >\n {{ 'Starts with' | translate }}\n </button>\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Contains' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-contains\"\n (click)=\"onFilter('*' + term + '*')\"\n >\n {{ 'Contains' | translate }}\n </button>\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Ends with' | translate }}\"\n type=\"button\"\n data-cy=\"search-input--search-ends-with\"\n (click)=\"onFilter('*' + term)\"\n >\n {{ 'Ends with' | translate }}\n </button>\n </div>\n </div>\n </c8y-li>\n\n <!-- Recent search -->\n <c8y-li\n *ngIf=\"term.length === 0 && recentSearchResults.length > 0\"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block\">\n <span translate>Recent search views</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n *ngFor=\"let result of term.length === 0 ? recentSearchResults : []\"\n (click)=\"open($event, result, result.name)\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- Recently registered devices -->\n <c8y-li\n *ngIf=\"\n term.length === 0 && (recentlyRegisteredResults$ | async)?.data?.length > 0 && !groupsOnly\n \"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block\">\n <span translate>Recently registered devices</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n *c8yFor=\"\n let result of term.length === 0 && !groupsOnly\n ? recentlyRegisteredResults$\n : { data: [] };\n loadMore: 'none'\n \"\n (click)=\"open($event, result, result.name)\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- Search results -->\n <c8y-li\n *ngIf=\"term.length !== 0\"\n [selectable]=\"false\"\n >\n <div class=\"legend form-block m-0\">\n <span translate>Search results</span>\n </div>\n </c8y-li>\n <c8y-li\n class=\"c8y-list__item--link m-l-16 m-r-16\"\n [title]=\"result.name\"\n *c8yFor=\"\n let result of results$;\n loadMore: 'auto';\n notFound: notFoundTemplate;\n loadingTemplate: loading;\n loadNextLabel: 'Find more\u2026'\n \"\n (click)=\"open($event, result, result.name)\"\n data-cy=\"search-input--search-results\"\n >\n <c8y-li-icon>\n <ng-container *ngIf=\"result | shouldShowMo: deviceType.DEVICE; else group\">\n <device-status [mo]=\"result\"></device-status>\n </ng-container>\n <ng-template #group>\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"result | getGroupIcon | async\"\n ></i>\n </ng-template>\n </c8y-li-icon>\n {{ result.name || '--' }}\n </c8y-li>\n\n <!-- No search results found entry -->\n <ng-template #notFoundTemplate>\n <c8y-ui-empty-state\n [icon]=\"'search'\"\n [title]=\"'No match found.' | translate\"\n data-cy=\"search-input--empty-state\"\n [ngClass]=\"{ 'p-4': mode === 'search' }\"\n [horizontal]=\"true\"\n *ngIf=\"noMatch\"\n >\n <small\n translate\n *ngIf=\"mode === 'search'\"\n >\n Try to filter or open the asset grid to show all devices and groups.\n </small>\n <small\n translate\n *ngIf=\"mode === 'select'\"\n >\n Try to rephrase your search word.\n </small>\n </c8y-ui-empty-state>\n </ng-template>\n\n <!-- loading bar first entries -->\n <c8y-li *ngIf=\"isLoading\">\n <c8y-loading></c8y-loading>\n </c8y-li>\n\n <!-- loading bar for loading more entries (inventory roles) -->\n <ng-template #loading>\n <c8y-li>\n <c8y-loading></c8y-loading>\n </c8y-li>\n </ng-template>\n\n <!-- more filter possibilities -->\n <c8y-li\n class=\"m-t-24 bg-level-2 p-t-16 p-b-16 p-l-24 p-r-24 sticky-bottom\"\n [selectable]=\"false\"\n *ngIf=\"mode === 'search'\"\n >\n <div class=\"d-flex a-i-center\">\n <i\n class=\"text-info m-r-4\"\n c8yIcon=\"info-circle\"\n ></i>\n <p\n class=\"m-r-8\"\n translate\n >\n Need more filter possibilities?\n </p>\n <button\n class=\"m-l-16 btn btn-default btn-sm\"\n title=\"{{ 'Go to the asset data table' | translate }}\"\n type=\"button\"\n (mousedown)=\"onOpenAssetTable()\"\n data-cy=\"search-input--asset-table-btn\"\n >\n {{ 'Go to the asset data table' | translate }}\n </button>\n </div>\n </c8y-li>\n </c8y-typeahead>\n </form>\n</ng-template>\n" }] }], ctorParameters: () => [{ type: i1.Router }, { type: i2.InventoryService }, { type: i3.InventorySearchService }, { type: i0.ChangeDetectorRef }], propDecorators: { mode: [{ type: Input }], enableCustomTemplatePlaceholder: [{ type: Input }], customPlaceholder: [{ type: Input }], externalTerm: [{ type: Input }], customDataQuery: [{ type: Input }], container: [{ type: Input }], groupsOnly: [{ type: Input }], filter: [{ type: Output }], search: [{ type: Output }], reset: [{ type: Output }], onClick: [{ type: Output }], typeahead: [{ type: ViewChild, args: [TypeaheadComponent, { static: false }] }], dropdown: [{ type: ViewChild, args: ['searchDropdown', { static: false }] }], onKeydownHandler: [{ type: HostListener, args: ['document:keydown', ['$event']] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VhcmNoLWlucHV0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2NvcmUvc2VhcmNoL3NlYXJjaC1pbnB1dC5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9jb3JlL3NlYXJjaC9zZWFyY2gtaW5wdXQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLGlCQUFpQixFQUNqQixTQUFTLEVBQ1QsWUFBWSxFQUNaLFlBQVksRUFDWixLQUFLLEVBQ0wsTUFBTSxFQUNOLFNBQVMsRUFDVixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDekMsT0FBTyxFQUFrQixnQkFBZ0IsRUFBdUIsTUFBTSxhQUFhLENBQUM7QUFDcEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzFDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdELE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBYyxFQUFFLEVBQUUsT0FBTyxFQUFpQixNQUFNLE1BQU0sQ0FBQztBQUNuRixPQUFPLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNsRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNuRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBTXBFLE1BQU0sT0FBTyxvQkFBb0I7SUFtQi9COztPQUVHO0lBQ0gsSUFDSSxlQUFlLENBQUMsS0FBcUU7UUFDdkYsSUFBSSxLQUFLLEVBQUUsQ0FBQztZQUNWLElBQUksQ0FBQyxXQUFXLEdBQUcsS0FB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RSxDQUFDLFFBQVEsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENoYW5nZURldGVjdG9yUmVmLFxuICBDb21wb25lbnQsXG4gIEV2ZW50RW1pdHRlcixcbiAgSG9zdExpc3RlbmVyLFxuICBJbnB1dCxcbiAgT3V0cHV0LFxuICBWaWV3Q2hpbGRcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBSb3V0ZXIgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgSU1hbmFnZWRPYmplY3QsIEludmVudG9yeVNlcnZpY2UsIElSZXN1bHRMaXN0LCBQYWdpbmcgfSBmcm9tICdAYzh5L2NsaWVudCc7XG5pbXBvcnQgeyBnZXR0ZXh0IH0gZnJvbSAnLi4vaTE4bi9nZXR0ZXh0JztcbmltcG9ydCB7IEJzRHJvcGRvd25EaXJlY3RpdmUgfSBmcm9tICduZ3gtYm9vdHN0cmFwL2Ryb3Bkb3duJztcbmltcG9ydCB7IGRlZmVyLCBlbXB0eSwgbWVyZ2UsIE9ic2VydmFibGUsIG9mLCBTdWJqZWN0LCBVbmFyeUZ1bmN0aW9uIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBzd2l0Y2hNYXAsIHRha2VVbnRpbCwgdGFwIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgTWFuYWdlZE9iamVjdFR5cGUgfSBmcm9tICcuLi9jb21tb24vbWFuYWdlZC1vYmplY3QtdHlwZSc7XG5pbXBvcnQgeyBUeXBlYWhlYWRDb21wb25lbnQgfSBmcm9tICcuLi9zZWxlY3QvdHlwZWFoZWFkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBJbnZlbnRvcnlTZWFyY2hTZXJ2aWNlIH0gZnJvbSAnLi9pbnZlbnRvcnktc2VhcmNoLnNlcnZpY2UnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdjOHktc2VhcmNoLWlucHV0JyxcbiAgdGVtcGxhdGVVcmw6ICcuL3NlYXJjaC1pbnB1dC5jb21wb25lbnQuaHRtbCdcbn0pXG5leHBvcnQgY2xhc3MgU2VhcmNoSW5wdXRDb21wb25lbnQge1xuICBASW5wdXQoKVxuICBtb2RlOiAnc2VhcmNoJyB8ICdzZWxlY3QnID0gJ3NlYXJjaCc7XG5cbiAgLyoqXG4gICAqIFVubG9ja3MgdGhlIGFiaWxpdHkgdG8gcGxhY2UgYSBjdXN0b20gdGVtcGxhdGUgdW5kZXIgdGhlIHNlYXJjaCBpbnB1dC5cbiAgICovXG4gIEBJbnB1dCgpIGVuYWJsZUN1c3RvbVRlbXBsYXRlUGxhY2Vob2xkZXIgPSBmYWxzZTtcblxuICAvKipcbiAgICogQSBjdXN0b20gcGxhY2Vob2xkZXIgaW4gdGhlIHNlYXJjaCBiYXIuXG4gICAqL1xuICBASW5wdXQoKSBjdXN0b21QbGFjZWhvbGRlcjogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBFdmVudCwgd2hpY2ggaXMgdXNlZCB0byBzZXQgYSBuZXcgZXh0ZXJuYWwgdGVybS4gUGFzc2luZyBudWxsIHdpbGwgcmUtZXhlY3V0ZSB0aGUgcXVlcnkgdG8gQkUuXG4gICAqIFRoaXMgYWxsb3dzIG5ldyBmaWx0ZXJzIHRvIGJlIGFwcGxpZWQgdG8gdGhlIGN1cnJlbnRseSBzZWxlY3RlZCB0ZXJtLlxuICAgKi9cbiAgQElucHV0KCkgZXh0ZXJuYWxUZXJtOiBFdmVudEVtaXR0ZXI8c3RyaW5nPjtcbiAgLyoqXG4gICAqIEEgY3VzdG9tIHF1ZXJ5IHNldHRlciB1c2VkIHRvIG92ZXJyaWRlIHRoZSBzdGFuZGFyZCBxdWVyeS4gSW4gb3JkZXIgdG8gb2J0YWluIGRhdGEuXG4gICAqL1xuICBASW5wdXQoKVxuICBzZXQgY3VzdG9tRGF0YVF1ZXJ5KHF1ZXJ5OiBVbmFyeUZ1bmN0aW9uPHN0cmluZywgT2JzZXJ2YWJsZTxJUmVzdWx0TGlzdDxJTWFuYWdlZE9iamVjdD4+Pikge1xuICAgIGlmIChxdWVyeSkge1xuICAgICAgdGhpcy5jdXN0b21RdWVyeSA9IHF1ZXJ5O1xuICAgIH1cbiAgfVxuICBASW5wdXQoKSBjb250YWluZXI6ICcnIHwgJ2JvZHknID0gJyc7XG5cbiAgQElucHV0KCkgZ3JvdXBzT25seSA9IGZhbHNlO1xuXG4gIEBPdXRwdXQoKVxuICBmaWx0ZXIgPSBuZXcgRXZlbnRFbWl0dGVyPHN0cmluZz4oKTtcblxuICBAT3V0cHV0KClcbiAgc2VhcmNoID0gbmV3IEV2ZW50RW1pdHRlcjxzdHJpbmc+KCk7XG5cbiAgQE91dHB1dCgpXG4gIHJlc2V0ID0gbmV3IEV2ZW50RW1pdHRlcjxJTWFuYWdlZE9iamVjdD4oKTtcblxuICBAT3V0cHV0KClcbiAgb25DbGljayA9IG5ldyBFdmVudEVtaXR0ZXI8SU1hbmFnZWRPYmplY3Q+KCk7XG5cbiAgZGV2aWNlVHlwZTogdHlwZW9mIE1hbmFnZWRPYmplY3RUeXBlID0gTWFuYWdlZE9iamVjdFR5cGU7XG4gIHRlcm0gPSAnJztcbiAgZGVmYXVsdFBsYWNlaG9sZGVyID0gZ2V0dGV4dCgnU2VhcmNoIGZvciBncm91cHMgb3IgYXNzZXRz4oCmJyk7XG4gIHNlbGVjdGVkO1xuXG4gIGN1c3RvbVF1ZXJ5OiAodGV4dDogc3RyaW5nKSA9PiBPYnNlcnZhYmxlPElSZXN1bHRMaXN0PElNYW5hZ2VkT2JqZWN0Pj47XG4gIHJlc3VsdHMkOiBPYnNlcnZhYmxlPElSZXN1bHRMaXN0PElNYW5hZ2VkT2JqZWN0Pj47XG4gIHJlY2VudFNlYXJjaFJlc3VsdHM6IElNYW5hZ2VkT2JqZWN0W10gPSBbXTtcbiAgcmVjZW50bHlSZWdpc3RlcmVkUmVzdWx0cyQ6IE9ic2VydmFibGU8SVJlc3VsdExpc3Q8SU1hbmFnZWRPYmplY3Q+PjtcbiAgaXNMb2FkaW5nID0gZmFsc2U7XG4gIG5vTWF0Y2ggPSBmYWxzZTtcblxuICBwcml2YXRlIHJlYWRvbmx5IFJFQ0VOVF9TRUFSQ0hfU1RPUkFHRV9LRVkgPSAncmVjZW50X3NlYXJjaF92aWV3JztcbiAgcHJpdmF0ZSByZWFkb25seSBNQVhfUkVDRU5UX1NFQVJDSF9SRVNVTFRTID0gNTtcbiAgcHJpdmF0ZSByZWFkb25seSBERUZBVUxUX0ZJTFRFUjogb2JqZWN0ID0ge1xuICAgIHdpdGhUb3RhbFBhZ2VzOiB0cnVlLFxuICAgIHBhZ2VTaXplOiA1LFxuICAgIHdpdGhDaGlsZHJlbjogZmFsc2VcbiAgfTtcbiAgcHJpdmF0ZSByZWFkb25seSBLRVlDT0RFX0VOVEVSID0gJ0VudGVyJztcbiAgcHJpdmF0ZSByZWFkb25seSBLRVlDT0RFX0VTQyA9ICdFc2NhcGUnO1xuICBwcml2YXRlIG9uRGVzdHJveSQgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuXG4gIEBWaWV3Q2hpbGQoVHlwZWFoZWFkQ29tcG9uZW50LCB7IHN0YXRpYzogZmFsc2UgfSlcbiAgcHJpdmF0ZSB0eXBlYWhlYWQ6IFR5cGVhaGVhZENvbXBvbmVudDtcblxuICBAVmlld0NoaWxkKCdzZWFyY2hEcm9wZG93bicsIHsgc3RhdGljOiBmYWxzZSB9KVxuICBwcml2YXRlIGRyb3Bkb3duOiBCc0Ryb3Bkb3duRGlyZWN0aXZlO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcm91dGVyOiBSb3V0ZXIsXG4gICAgcHJpdmF0ZSBpbnZlbnRvcnk6IEludmVudG9yeVNlcnZpY2UsXG4gICAgcHJpdmF0ZSBpbnZlbnRvcnlTZWFyY2hTZXJ2aWNlOiBJbnZlbnRvcnlTZWFyY2hTZXJ2aWNlLFxuICAgIHByaXZhdGUgY2Q6IENoYW5nZURldGVjdG9yUmVmXG4gICkge31cblxuICBhc3luYyBuZ09uSW5pdCgpIHtcbiAgICBjb25zdCByZWNlbnRTZWFyY2hJZHMgPSBKU09OLnBhcnNlKGxvY2FsU3RvcmFnZS5nZXRJdGVtKHRoaXMuUkVDRU5UX1NFQVJDSF9TVE9SQUdFX0tFWSkpO1xuICAgIGlmIChyZWNlbnRTZWFyY2hJZHMgJiYgcmVjZW50U2VhcmNoSWRzLmxlbmd0aCA+IDApIHtcbiAgICAgIGNvbnN0IHsgZGF0YSB9ID0gYXdhaXQgdGhpcy5pbnZlbnRvcnkubGlzdCh7IGlkczogcmVjZW50U2VhcmNoSWRzLmpvaW4oJywnKSB9KTtcbiAgICAgIHRoaXMucmVjZW50U2VhcmNoUmVzdWx0cyA9IHRoaXMuZ3JvdXBzT25seVxuICAgICAgICA/IHRoaXMuaW52ZW50b3J5U2VhcmNoU2VydmljZS5maWx0ZXJPbmx5R3JvdXBzKGRhdGEpXG4gICAgICAgIDogZGF0YTtcbiAgICAgIHRoaXMucmVjZW50bHlSZWdpc3RlcmVkUmVzdWx0cyQgPSBkZWZlcigoKSA9PlxuICAgICAgICB0aGlzLmludmVudG9yeS5saXN0KHtcbiAgICAgICAgICBxOiAnJG9yZGVyYnk9Y3JlYXRpb25UaW1lIGRlc2MnLFxuICAgICAgICAgIC4uLnRoaXMuREVGQVVMVF9GSUxURVJcbiAgICAgICAgfSlcbiAgICAgICk7XG4gICAgfVxuICAgIGlmICh0aGlzLm1vZGUgPT09ICdzZWxlY3QnKSB7XG4gICAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoKCkgPT4ge1xuICAgICAgICB0aGlzLnN1YnNjcmliZU9uU2VhcmNoKCk7XG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5leHRlcm5hbFRlcm0pIHtcbiAgICAgIHRoaXMuZXh0ZXJuYWxUZXJtLnBpcGUodGFrZVVudGlsKHRoaXMub25EZXN0cm95JCkpLnN1YnNjcmliZSh0ZXJtID0+IHtcbiAgICAgICAgaWYgKHRlcm0gPT09IG51bGwpIHtcbiAgICAgICAgICB0aGlzLnR5cGVhaGVhZC5vblNlYXJjaC5lbWl0KHRoaXMudGVybSk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMudHlwZWFoZWFkLm9uU2VhcmNoLmVtaXQodGVybSk7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICBASG9zdExpc3RlbmVyKCdkb2N1bWVudDprZXlkb3duJywgWyckZXZlbnQnXSkgb25LZXlkb3duSGFuZGxlcihldmVudDogS2V5Ym9hcmRFdmVudCkge1xuICAgIGlmIChldmVudC5rZXkgPT09IHRoaXMuS0VZQ09ERV9FU0MpIHtcbiAgICAgIHRoaXMuaGlkZURyb3Bkb3duKCk7XG4gICAgfVxuICB9XG5cbiAgb25PcGVuQ2hhbmdlKGlzT3BlbjogYm9vbGVhbik6IHZvaWQge1xuICAgIGlmIChpc09wZW4pIHtcbiAgICAgIC8vIG5lZWRzIHRvIHJlcXVlc3QgYW4gYW5pbWF0aW9uIGZyYW1lIGFzXG4gICAgICAvLyBvdGhlcndpc2UgdGhlIHR5cGVhaGVhZCBpcyB1bmRlZmluZWRcbiAgICAgIHJlcXVlc3RBbmltYXRpb25GcmFtZSgoKSA9PiB7XG4gICAgICAgIHRoaXMuc3Vic2NyaWJlT25TZWFyY2goKTtcbiAgICAgICAgdGhpcy50eXBlYWhlYWQuZHJvcGRvd24uc2hvdygpO1xuICAgICAgICB0aGlzLnR5cGVhaGVhZC5zZWFyY2hDb250cm9sLm5hdGl2ZUVsZW1lbnQuZm9jdXMoKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIG9wZW4oZXZlbnQ6IEV2ZW50LCBtbzogSU1hbmFnZWRPYmplY3QsIHRlcm0/KSB7XG4gICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgY29uc3QgaXNBbHJlYWR5UmVjZW50ID0gdGhpcy5yZWNlbnRTZWFyY2hSZXN1bHRzLmZpbmQoKHsgaWQgfSkgPT4gaWQgPT09IG1vLmlkKTtcbiAgICBpZiAoIWlzQWxyZWFkeVJlY2VudCkge1xuICAgICAgdGhpcy5yZWNlbnRTZWFyY2hSZXN1bHRzLnVuc2hpZnQobW8pO1xuICAgICAgdGhpcy5yZWNlbnRTZWFyY2hSZXN1bHRzID0gdGhpcy5yZWNlbnRTZWFyY2hSZXN1bHRzLnNsaWNlKDAsIHRoaXMuTUFYX1JFQ0VOVF9TRUFSQ0hfUkVTVUxUUyk7XG4gICAgfVxuICAgIGNvbnN0IHJlY2VudFNlYXJjaElkcyA9IHRoaXMucmVjZW50U2VhcmNoUmVzdWx0cy5tYXAoKHsgaWQgfSkgPT4gaWQpO1xuICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKHRoaXMuUkVDRU5UX1NFQVJDSF9TVE9SQUdFX0tFWSwgSlNPTi5zdHJpbmdpZnkocmVjZW50U2VhcmNoSWRzKSk7XG4gICAgaWYgKHRlcm0pIHtcbiAgICAgIHRoaXMuc2VsZWN0ZWQgPSBtbztcbiAgICAgIHRoaXMudGVybSA9IHRlcm07XG4gICAgfVxuICAgIHRoaXMub25DbGljay5lbWl0KG1vKTtcbiAgICB0aGlzLmhpZGVEcm9wZG93bigpO1xuICB9XG5cbiAgb25SZXNldChzdGF0dXM6IHsgaWNvbjogc3RyaW5nOyAkZXZlbnQ6IE1vdXNlRXZlbnQgfSkge1xuICAgIHN0YXR1cy4kZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgdGhpcy5yZXNldC5lbWl0KHRoaXMuc2VsZWN0ZWQpO1xuICAgIHRoaXMudHlwZWFoZWFkLm9uU2VhcmNoLmVtaXQoJycpO1xuICAgIHRoaXMuc2VsZWN0ZWQgPSB1bmRlZmluZWQ7XG4gICAgdGhpcy50eXBlYWhlYWQuc2VhcmNoQ29udHJvbC5uYXRpdmVFbGVtZW50LmZvY3VzKCk7XG4gIH1cblxuICBrZXlEb3duKGV2ZW50OiBLZXlib2FyZEV2ZW50KSB7XG4gICAgaWYgKGV2ZW50LmtleSA9PT0gdGhpcy5LRVlDT0RFX0VOVEVSKSB7XG4gICAgICAvLyBlbnRlciBoaXQgY2FuIGJlIGZhc3RlciB0aGVuIHR5cGVhaGVhZCBkZWJvdW5jZSxcbiAgICAgIC8vIHRoZXJlZm9yZSB3ZSB0YWtlIHRoZSB0ZXJtIGZyb20gdGhlIERPTSBlbGVtZW50XG4gICAgICAvLyBpdHNlbGY6XG4gICAgICBjb25zdCBzZWFyY2hUZXJtID0gKGV2ZW50LnRhcmdldCBhcyBhbnkpLnZhbHVlO1xuICAgICAgdGhpcy5vblNlYXJjaChzZWFyY2hUZXJtKTtcbiAgICB9XG4gIH1cblxuICBvblNlYXJjaChzZWFyY2g6IHN0cmluZykge1xuICAgIHRoaXMuc2VhcmNoLmVtaXQoc2VhcmNoKTtcbiAgICB0aGlzLmhpZGVEcm9wZG93bigpO1xuICB9XG5cbiAgb25GaWx0ZXIoc2VhcmNoOiBzdHJpbmcpIHtcbiAgICB0aGlzLmZpbHRlci5lbWl0KHNlYXJjaCk7XG4gICAgdGhpcy5oaWRlRHJvcGRvd24oKTtcbiAgfVxuXG4gIG9uT3BlbkFzc2V0VGFibGUoKSB7XG4gICAgdGhpcy5yb3V0ZXIubmF2aWdhdGVCeVVybCgnL2Fzc2V0c2VhcmNoJyk7XG4gICAgdGhpcy5oaWRlRHJvcGRvd24oKTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCkge1xuICAgIGlmICh0aGlzLm9uRGVzdHJveSQpIHtcbiAgICAgIHRoaXMub25EZXN0cm95JC5uZXh0KCk7XG4gICAgICB0aGlzLm9uRGVzdHJveSQuY29tcGxldGUoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGhpZGVEcm9wZG93bigpIHtcbiAgICBpZiAodGhpcy5kcm9wZG93bikge1xuICAgICAgdGhpcy5kcm9wZG93bi5oaWRlKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKHRoaXMudHlwZWFoZWFkICYmIHRoaXMudHlwZWFoZWFkLmRyb3Bkb3duKSB7XG4gICAgICB0aGlzLnR5cGVhaGVhZC5kcm9wZG93bi5oaWRlKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzdWJzY3JpYmVPblNlYXJjaCgpIHtcbiAgICBpZiAoIXRoaXMucmVzdWx0cyQpIHtcbiAgICAgIHRoaXMucmVzdWx0cyQgPSB0aGlzLnR5cGVhaGVhZC5vblNlYXJjaC5waXBlKFxuICAgICAgICB0YXAodGVybSA9PiB0aGlzLm9uVHlwaW5nU3RhcnRlZCh0ZXJtKSksXG4gICAgICAgIHN3aXRjaE1hcCh0ZXJtID0+IHRoaXMubWVyZ2VSZXF1ZXN0KHRlcm0pKSxcbiAgICAgICAgdGFwKCgpID0+IHR