UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

2,067 lines 116 kB
import * as i1 from '@c8y/ngx-components';
import { NavigatorNode, DeviceStatusComponent, GroupFragment, ModalService, AlertService, BreadcrumbService, AppStateService, OptionsService, ViewContext, Permissions, IconDirective, C8yTranslateDirective, C8yTranslatePipe, SearchInputComponent, GetGroupIconPipe, LoadingComponent, EmptyStateComponent, CoreModule, CommonModule, CoreSearchModule, ModalModule, DeviceStatusModule, hookNavigator } from '@c8y/ngx-components';
import { gettext } from '@c8y/ngx-components/gettext';
import { debounce, get } from 'lodash-es';
import { BehaviorSubject, Subject, empty } from 'rxjs';
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, inject, Optional, Inject, EventEmitter, Output, Input, Component, forwardRef, ViewChild, NgModule } from '@angular/core';
import { Router, ActivationEnd } from '@angular/router';
import * as i2 from '@c8y/client';
import { InventoryService, UserService, QueriesUtil } from '@c8y/client';
import { ApiService } from '@c8y/ngx-components/api';
import { filter, first, mergeMap, takeUntil, tap } from 'rxjs/operators';
import * as i1$1 from '@ngx-translate/core';
import { TranslateService } from '@ngx-translate/core';
import { CollapseDirective, CollapseModule } from 'ngx-bootstrap/collapse';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { isArray, isNumber, isString, isObject } from 'lodash';
import { NgIf, NgStyle, NgClass, NgFor, AsyncPipe } from '@angular/common';
import * as i4 from '@angular/forms';
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';

var AssetsNavigatorAction;
(function (AssetsNavigatorAction) {
    AssetsNavigatorAction[AssetsNavigatorAction["FETCH"] = 0] = "FETCH";
    AssetsNavigatorAction[AssetsNavigatorAction["NEXT"] = 1] = "NEXT";
    AssetsNavigatorAction[AssetsNavigatorAction["REFRESH"] = 2] = "REFRESH";
    AssetsNavigatorAction[AssetsNavigatorAction["LOADING_DONE"] = 3] = "LOADING_DONE";
})(AssetsNavigatorAction || (AssetsNavigatorAction = {}));

class LoadMoreNode extends NavigatorNode {
    static { this.NAME = 'LoadMoreNode'; }
    constructor() {
        super();
        this.label = gettext('Load more');
        this.icon = 'plus';
        this.droppable = true;
        this.priority = -Infinity;
    }
    toString() {
        return LoadMoreNode.NAME;
    }
    isGroup() {
        return false;
    }
}

class AssetNode extends NavigatorNode {
    static { this.NAME = 'AssetNode'; }
    get hasChildren() {
        return this.root || this.service.isGroup(this.mo);
    }
    get isDevice() {
        return !!this.mo.c8y_IsDevice;
    }
    get isDeviceOrProbablyChildDevice() {
        return this.isDevice || this.isNeitherDeviceOrGroup;
    }
    get isNeitherDeviceOrGroup() {
        return (!this.service.isGroup(this.mo) &&
            !this.service.isDynamicGroup(this.mo) &&
            !this.isDevice &&
            !this.root);
    }
    constructor(service, config = {}) {
        super(config);
        this.service = service;
        this.config = config;
        this.hideDevices = false;
        this.filterQuery$ = new BehaviorSubject('');
        this.showChildDevices = false;
        /**
         * Asset node children (subentries).
         */
        this.children = [];
        this.nodesFetched = new Subject();
        this.root = this.root || false;
        this.hideDevices = config.hideDevices ?? this.hideDevices;
        this.mo = this.mo || {};
        this.path = this.getPath();
        this.draggable = !this.service?.moduleConfig?.disableDragAndDrop && !this.root;
        this.droppable =
            !this.service?.moduleConfig?.disableDragAndDrop && !this.isDeviceOrProbablyChildDevice;
        this.routerLinkExact = this.root;
        this.updateIcon(false);
        this.onUpdateSubscription = this.service
            .onUpdate(this)
            .subscribe(({ data, method }) => this.refresh(data, method));
        this.setLabel();
        this.iconComponent = this.isDeviceOrProbablyChildDevice ? DeviceStatusComponent : undefined;
    }
    getPath() {
        if (this.config.path) {
            return this.config.path;
        }
        return this.root
            ? 'group'
            : this.isDeviceOrProbablyChildDevice
                ? `device/${this.mo.id}`
                : `group/${this.mo.id}`;
    }
    refresh(mo = {}, method = 'GET') {
        if (mo?.id === this.mo.id) {
            this.mo = mo;
            this.setLabel();
            if (this.refreshCallback) {
                this.refreshCallback();
            }
        }
        else if (method === 'DELETE') {
            this.parents.forEach((node) => node.refresh());
            return;
        }
        if (this.events) {
            this.events.next(AssetsNavigatorAction.REFRESH);
        }
    }
    setLabel() {
        if (this.config.label || this.root) {
            this.label = this.config.label || gettext('Groups');
            this.translateLabel = true;
        }
        else {
            this.label = this.service.label(this.mo);
            this.translateLabel = false;
        }
    }
    click(options = {}) {
        if (this.isDeviceOrProbablyChildDevice && !this.showChildDevices) {
            this.service.preferBreadcrumb(this.parents);
            return;
        }
        this.hookEvents();
        this.updateIcon(options.open);
        if (options.open) {
            this.events.next(AssetsNavigatorAction.FETCH);
        }
    }
    sort() {
        this.children.sort((a, b) => {
            if (a.priority > b.priority) {
                return -1;
            }
            else if (a.priority < b.priority) {
                return 1;
            }
            else {
                return 0;
            }
        });
    }
    addManagedObject(mo) {
        const { childAdditions } = this.mo;
        if (!this.isChildAddition(childAdditions, mo)) {
            this.add(this.service.createChildNode(mo, { hideDevices: this.hideDevices }));
        }
    }
    isChildAddition(childAdditions, mo) {
        return (childAdditions && childAdditions.references.some(({ managedObject: { id } }) => id === mo.id));
    }
    destroy() {
        this.onUpdateSubscription.unsubscribe();
    }
    get canDrop() {
        const nodeToMove = this.service.draggedData;
        if (nodeToMove) {
            const shouldGetChildOfItsOwn = !!nodeToMove.find(child => child === this);
            const isAlreadyChild = this.children.some(child => child.mo && child.mo.id === nodeToMove.mo.id);
            const preventMove = this === nodeToMove || shouldGetChildOfItsOwn || isAlreadyChild;
            return this.droppable && !preventMove && this.service.canDropNode(this.root);
        }
        return this.droppable;
    }
    dragStart($event) {
        super.dragStart($event);
        this.service.draggedData = this;
        this.service.rootNode.droppable = !this.isDeviceOrProbablyChildDevice;
    }
    dragEnd($event) {
        super.dragEnd($event);
    }
    async drop($event) {
        const nodeToMove = this.service.draggedData;
        // TODO remove when asset type node can be used on the root level.
        if (this.root && this.isAsset(nodeToMove)) {
            this.service.alert.info(gettext('Asset type node cannot become root node.'));
            this.draggedHover = false;
            this.service.draggedData = undefined;
            return;
        }
        super.drop($event);
        if (this.canDrop) {
            await this.moveNode(nodeToMove);
        }
        else {
            this.draggedHover = false;
            this.service.draggedData = undefined;
        }
    }
    hookEvents() {
        if (!this.events) {
            this.events = new Subject();
            this.events.subscribe(evt => {
                if (!this.loading) {
                    this.handleEvent(evt);
                }
            });
        }
    }
    toString() {
        return AssetNode.NAME;
    }
    /**
     * Checks if the current node has child devices.
     */
    hasChildDevices() {
        return this.mo && this.mo.c8y_IsDevice && this.mo.childDevices.references.length > 0;
    }
    fetch() {
        return this.root
            ? this.service.getRootNodes()
            : this.service.getGroupItems(this.mo.id, this.hideDevices
                ? {
                    query: `$filter=(has(${GroupFragment.groupFragmentType}))$orderby=name`
                }
                : {});
    }
    async updateIcon(open) {
        this.icon = await this.service.icon(
        // if it's root we are going to pass a fake mo to get the same icon as groups
        this.root ? { c8y_IsDeviceGroup: {} } : this.mo, open);
    }
    countChildren() {
        return this.children.length;
    }
    async handleEvent(evt) {
        if (!this.countChildren() && evt === AssetsNavigatorAction.FETCH) {
            this.loading = true;
            this.addNodes(await this.fetch());
            this.loading = false;
        }
        else if (evt === AssetsNavigatorAction.NEXT) {
            this.loadMoreNode.loading = true;
            this.addNodes(await this.paging.next());
            this.loadMoreNode.loading = false;
        }
        else if (evt === AssetsNavigatorAction.REFRESH) {
            this.loading = false;
            this.paging = undefined;
            this.loadMoreNode = undefined;
            this.empty();
            this.events.next(AssetsNavigatorAction.FETCH);
        }
    }
    addNodes(res) {
        if (res.paging) {
            const { currentPage, nextPage, pageSize } = (this.paging = res.paging);
            if (currentPage === 1) {
                this.empty();
            }
            const itemsCount = res.data.length;
            const moreItemsAvailable = !!nextPage && itemsCount === pageSize;
            this.toggleLoadMore(moreItemsAvailable);
        }
        (res.data || res).map(mo => {
            return this.addManagedObject(mo);
        });
        this.events.next(AssetsNavigatorAction.LOADING_DONE);
        this.nodesFetched.next();
    }
    toggleLoadMore(show) {
        if (!this.loadMoreNode && show) {
            this.loadMoreNode = new LoadMoreNode();
            this.add(this.loadMoreNode);
            this.loadMoreNode.click = debounce(() => this.events.next(AssetsNavigatorAction.NEXT), 300, {
                leading: true,
                trailing: false
            });
        }
        if (this.loadMoreNode) {
            this.loadMoreNode.hidden = !show;
        }
    }
    async moveNode(nodeToMove) {
        try {
            const isCopy = await this.showDropConfirm(nodeToMove);
            await this.verifyNodeAccess(nodeToMove);
            await this.addMovedNode(nodeToMove);
            if (!isCopy) {
                await this.removeMovedNode(nodeToMove);
            }
            this.expand();
        }
        catch (ex) {
            if (ex) {
                this.service.alert.addServerFailure(ex);
            }
        }
        finally {
            this.draggedHover = false;
            this.service.draggedData = undefined;
        }
    }
    async showDropConfirm(nodeToMove) {
        this.confirm.title = gettext('Move');
        this.confirm.message = gettext('Do you want to move the group?');
        const buttons = [
            {
                label: gettext('Cancel'),
                action: () => Promise.reject()
            },
            {
                label: gettext('Move'),
                status: 'default',
                action: () => Promise.resolve(false)
            }
        ];
        if (nodeToMove.isDeviceOrProbablyChildDevice) {
            this.confirm.title = gettext('Move or add');
            this.confirm.message = gettext('Do you want to move or add the device?');
            buttons.push({
                label: gettext('Add'),
                status: 'primary',
                action: () => Promise.resolve(true)
            });
        }
        return this.confirm.show(buttons);
    }
    async verifyNodeAccess(nodeToMove) {
        return this.service.inventory.update({ id: nodeToMove.mo.id });
    }
    async addMovedNode(nodeToMove) {
        let mo;
        if (this.root && !this.isAsset(nodeToMove)) {
            mo = (await this.service.inventory.update({
                id: nodeToMove.mo.id,
                type: GroupFragment.groupType
            })).data;
            this.addManagedObject(mo);
            return;
        }
        mo = (await this.service.inventory.childAssetsAdd(nodeToMove.mo, this.mo)).data;
        this.addManagedObject(mo);
    }
    isAsset(nodeToMove) {
        // TODO use isAsset check when https://github.com/Cumulocity-IoT/cumulocity-ui/pull/690 is merged.
        // Do not override asset type!
        return nodeToMove.mo?.c8y_IsAsset;
    }
    async removeMovedNode(nodeToMove) {
        for (const parent of nodeToMove.parents) {
            if (parent.mo && parent.mo.type === GroupFragment.dynamicGroupType) {
                break; // smart groups don't need to be changed
            }
            if (parent.root && !this.isAsset(nodeToMove)) {
                await this.service.inventory.update({
                    id: nodeToMove.mo.id,
                    type: GroupFragment.subGroupType
                });
            }
            if (!parent.root) {
                await this.service.inventory.childAssetsRemove(nodeToMove.mo, parent.mo);
            }
            parent.remove(nodeToMove);
        }
    }
}

class DynamicGroupNode extends AssetNode {
    constructor(service, config = {}) {
        super(service, config);
        this.service = service;
        this.draggable = false;
        this.droppable = false;
    }
    get hasChildren() {
        return true;
    }
    get query() {
        return this.mo.c8y_DeviceQueryString;
    }
    fetch() {
        return this.service.getDynamicGroupItems(this.query);
    }
}

const ASSET_NAVIGATOR_CONFIG = new InjectionToken('AssetNodeConfig');

/**
 * @deprecated
 * Original service was moved to core/common.
 * This service is deprecated and serves as a proxy to maintain backward compatibility.
 */
class DeviceGroupService {
    constructor(migratedDeviceGroupService) {
        this.migratedDeviceGroupService = migratedDeviceGroupService;
    }
    icon(mo, open = false) {
        return this.migratedDeviceGroupService.getIcon(mo, open);
    }
    isGroup(mo) {
        return this.migratedDeviceGroupService.isGroup(mo);
    }
    isDynamicGroup(mo) {
        return this.migratedDeviceGroupService.isDynamicGroup(mo);
    }
    isDataBroker(mo) {
        return this.migratedDeviceGroupService.isDataBroker(mo);
    }
    isDataBrokerActive(mo) {
        return this.migratedDeviceGroupService.isDataBrokerActive(mo);
    }
    isAsset(mo) {
        return this.migratedDeviceGroupService.isAsset(mo);
    }
    isAnyGroup(mo) {
        return this.migratedDeviceGroupService.isAnyGroup(mo);
    }
    isDevice(mo) {
        return this.migratedDeviceGroupService.isDevice(mo);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceGroupService, deps: [{ token: i1.GroupService }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceGroupService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceGroupService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }], ctorParameters: () => [{ type: i1.GroupService }] });

class AssetNodeService {
    constructor() {
        this.firstUrl = true;
        this.PAGE_SIZE = 20;
        this.inventory = inject(InventoryService);
        this.apiService = inject(ApiService);
        this.modal = inject(ModalService);
        this.alert = inject(AlertService);
        this.translateService = inject(TranslateService);
        this.breadcrumbService = inject(BreadcrumbService);
        this.user = inject(UserService);
        this.appState = inject(AppStateService);
        this.optionsService = inject(OptionsService);
        this.deviceGroupService = inject(DeviceGroupService);
        this.router = inject(Router);
        this.moduleConfig = {
            rootNodePriority: 2000,
            ...(inject(ASSET_NAVIGATOR_CONFIG, { optional: true }) || {})
        };
        this.queriesUtil = new QueriesUtil();
        this.router.events
            .pipe(filter((event) => event instanceof ActivationEnd && event.snapshot.data?.contextData), first())
            .subscribe(({ snapshot }) => {
            this.expandNodesOnStart(snapshot);
        });
    }
    /**
     * Expands the navigator nodes on first navigation.
     * @param snapshot The current navigation snapshot.
     */
    async expandNodesOnStart(snapshot) {
        if (!this.rootNode) {
            return;
        }
        if (snapshot.data?.context === ViewContext.Group ||
            snapshot.data?.context === ViewContext.Device) {
            const { data } = await this.inventory.detail(snapshot.data.contextData, {
                withParents: true
            });
            const allManagedObjectParentIds = [
                ...data.assetParents.references.map(({ managedObject }) => {
                    return managedObject.id;
                }),
                data.id
            ];
            this.expandAll(this.rootNode, allManagedObjectParentIds);
            return;
        }
        if (this.moduleConfig.openOnStart) {
            this.expandAll(this.rootNode, []);
        }
    }
    /**
     * Expands all the given ids recursively. Stops if it does not find any.
     * @param node The node where the expanding should be started
     * @param ids The ids that should be expanded.
     */
    expandAll(node, ids) {
        node.open = true;
        node.click({ open: true });
        if (node.events) {
            node.events
                .pipe(filter(action => action === AssetsNavigatorAction.LOADING_DONE), first())
                .subscribe(() => {
                const nodeToExpand = node.children.find(childNode => childNode.mo?.id && ids.includes(childNode.mo.id));
                if (nodeToExpand) {
                    this.expandAll(nodeToExpand, ids.filter(id => id !== nodeToExpand.mo.id));
                }
            });
        }
    }
    label(mo) {
        return (mo.name ||
            (this.isDevice(mo) &&
                this.translateService.instant(gettext('Device {{id}}'), { id: mo.id })) ||
            '--');
    }
    icon(mo, open) {
        return this.deviceGroupService.icon(mo, open);
    }
    isGroup(mo) {
        return this.deviceGroupService.isGroup(mo);
    }
    isDynamicGroup(mo) {
        return this.deviceGroupService.isDynamicGroup(mo);
    }
    isDataBroker(mo) {
        return this.deviceGroupService.isDataBroker(mo);
    }
    isDataBrokerActive(mo) {
        return this.deviceGroupService.isDataBrokerActive(mo);
    }
    isAsset(mo) {
        return this.deviceGroupService.isAsset(mo);
    }
    isAnyGroup(mo) {
        return this.deviceGroupService.isAnyGroup(mo);
    }
    isDevice(mo) {
        return this.deviceGroupService.isDevice(mo);
    }
    createRootNode(config = {}) {
        this.rootNode = this.createAssetNode({
            root: true,
            ...config,
            priority: this.moduleConfig.rootNodePriority,
            featureId: 'groups'
        });
        return this.rootNode;
    }
    createDynamicGroupNode(config) {
        return new DynamicGroupNode(this, config);
    }
    createAssetNode(config) {
        return new AssetNode(this, config);
    }
    createChildNode(managedObject, config) {
        const { type } = managedObject;
        config.mo = managedObject;
        if (type === GroupFragment.dynamicGroupType) {
            return this.createDynamicGroupNode(config);
        }
        return this.createAssetNode(config);
    }
    getRootNodes(customFilter) {
        const defaultFilter = {
            pageSize: this.PAGE_SIZE,
            withChildren: false,
            onlyRoots: !this.optionsService.disableOnlyRootsQuery,
            query: this.queriesUtil.buildQuery(this.navRootQueryFilter())
        };
        const groupFilter = { ...defaultFilter, ...customFilter };
        // due to BE performance limitations we do not allow filtering and sorting for a user without inventory roles
        if (this.appState?.currentUser?.value &&
            !this.user.hasAnyRole(this.appState.currentUser.value, [
                Permissions.ROLE_INVENTORY_READ,
                Permissions.ROLE_MANAGED_OBJECT_READ
            ])) {
            delete groupFilter.query;
            Object.assign(groupFilter, {
                fragmentType: GroupFragment.groupFragmentType,
                onlyRoots: true
            });
        }
        return this.inventory.list(this.createFilter(groupFilter));
    }
    getAllInventories(customFilter) {
        const defaultFilter = {
            pageSize: this.PAGE_SIZE,
            withChildren: false
        };
        const groupFilter = { ...defaultFilter, ...customFilter };
        return this.inventory.list(this.createFilter(groupFilter));
    }
    getGroupItems(moId, extraFilter = {}, withChildren = false, filterQuery = '') {
        const queryFilter = {
            withChildren,
            pageSize: this.PAGE_SIZE,
            query: this.groupQueryFilter(moId, filterQuery)
        };
        return this.inventory.childAssetsList(moId, { ...queryFilter, ...extraFilter });
    }
    getUnassignedDevices(withChildren = false, filterQuery = '') {
        const queryFilter = {
            fragmentType: 'c8y_IsDevice',
            onlyRoots: true,
            withChildren,
            pageSize: this.PAGE_SIZE,
            q: this.getUnassignedDevicesQueryStr(filterQuery)
        };
        return this.inventory.list(this.createFilter(queryFilter));
    }
    getDynamicGroupItems(groupQuery, filterObj = {}) {
        const { query, ...queryParams } = filterObj;
        const orderByQuery = query;
        const queryFilter = {
            q: this.buildCombinedQuery(groupQuery, orderByQuery),
            ...queryParams
        };
        return this.inventory.list(this.createFilter(queryFilter));
    }
    getDeviceChildren(moId, extraFilter = {}, filterQuery = '', withChildren = false) {
        const queryFilter = {
            withChildren,
            pageSize: this.PAGE_SIZE,
            query: this.groupQueryFilter(moId, filterQuery)
        };
        return this.inventory.childDevicesList(moId, { ...queryFilter, ...extraFilter });
    }
    getUnassignedDevicesQueryStr(filterQuery) {
        const hasGroupId = filterQuery.includes('bygroupid');
        // Fetch all unassigned devices.
        const defaultQueryStr = '$orderby=name';
        // filterQuery is a custom query to fetch unassigned devices filtered by name.
        return hasGroupId || !filterQuery ? defaultQueryStr : filterQuery;
    }
    groupQueryFilter(moId, filterQuery) {
        if (!filterQuery) {
            return `$filter=(bygroupid(${moId}))$orderby=name`;
        }
        return filterQuery;
    }
    navRootQueryFilter() {
        const navRootFilter = this.rootQueryFilter();
        navRootFilter.__orderby = [{ name: 1 }];
        return navRootFilter;
    }
    rootQueryFilter() {
        const { moduleConfig } = this;
        const rootFilter = this.optionsService.disableOnlyRootsQuery
            ? {
                __filter: {
                    type: GroupFragment.groupType
                },
                __orderby: []
            }
            : {
                __filter: {
                    __has: GroupFragment.groupFragmentType
                },
                __orderby: []
            };
        if (moduleConfig.smartGroups) {
            const queryFilter = {
                __filter: {
                    __and: [
                        {
                            type: GroupFragment.dynamicGroupType
                        },
                        {
                            __has: GroupFragment.dynamicGroupFragment
                        },
                        { __not: { __has: `${GroupFragment.dynamicGroupFragment}.invisible` } }
                    ]
                }
            };
            this.queriesUtil.addOrFilter(rootFilter, queryFilter);
        }
        return rootFilter;
    }
    onUpdate({ mo, root }) {
        if (mo.id) {
            return this.apiService
                .hookResponse(({ url, method }) => ['PUT', 'DELETE', 'POST'].includes(method) &&
                RegExp(`inventory/managedObjects/${mo.id}`).test(url))
                .pipe(filter(() => !this.draggedData), this.apiService.excludePermissionCall(), mergeMap((this.apiService.resolveData)), filter(response => !response?.data?.c8y_Dashboard));
        }
        else if (root) {
            return this.apiService
                .hookResponse(({ url, method }) => RegExp('inventory/managedObjects/?$').test(url) && method === 'POST')
                .pipe(mergeMap((this.apiService.resolveData)), filter(response => this.isNewManagedObjectRoot(response)));
        }
        else {
            return empty();
        }
    }
    isNewManagedObjectRoot(response = {}) {
        const { data } = response;
        let isRootAsset = false;
        if (typeof data === 'object') {
            isRootAsset = !!data[GroupFragment.groupFragmentType];
            if (!isRootAsset && this.moduleConfig.smartGroups) {
                isRootAsset = !!data[GroupFragment.dynamicGroupFragment];
            }
        }
        return isRootAsset;
    }
    /**
     * Check if it is possible to drop a node after dragging.
     * @param dropOnRoot Is the drop performed on the root node
     */
    canDropNode(dropOnRoot) {
        return (!dropOnRoot ||
            this.user.hasAnyRole(this.appState.currentUser.value, [
                Permissions.ROLE_INVENTORY_ADMIN,
                Permissions.ROLE_MANAGED_OBJECT_ADMIN
            ]));
    }
    /**
     * There could be multiple breadcrumbs for devices,
     * so we set a preferred one on click on a device.
     * @param parents The parent nodes of the device to select the prefered one.
     */
    preferBreadcrumb(parents) {
        if (parents.length === 1) {
            this.breadcrumbService.selectPreferredByPath(parents[0].path);
        }
    }
    createFilter(extraParams = {}) {
        const params = {
            currentPage: 1,
            withTotalPages: true,
            pageSize: 10
        };
        return { ...params, ...extraParams };
    }
    buildCombinedQuery(queryA, queryB) {
        let combinedQuery;
        if (queryA && queryB) {
            const filterQuery = this.queriesUtil.buildQuery([
                {
                    __useFilterQueryString: queryA
                },
                {
                    __useFilterQueryString: queryB
                }
            ]);
            const orberByQuery = this.queriesUtil.extractAndMergeOrderBys([queryA, queryB]);
            combinedQuery = `${filterQuery} ${orberByQuery}`;
        }
        else {
            combinedQuery = queryA || queryB || '';
        }
        return combinedQuery;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }], ctorParameters: () => [] });

class AssetNodeFactory {
    constructor(service, moduleConfig) {
        this.service = service;
        this.moduleConfig = moduleConfig;
    }
    get() {
        const rootNavigatorNode = get(this.moduleConfig, 'rootNavigatorNode') ?? true;
        let { rootNode } = this.service;
        if (rootNavigatorNode === false) {
            return;
        }
        if (!rootNode) {
            rootNode = this.service.createRootNode(rootNavigatorNode === true ? {} : rootNavigatorNode);
        }
        return rootNode;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeFactory, deps: [{ token: AssetNodeService }, { token: ASSET_NAVIGATOR_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeFactory }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetNodeFactory, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: AssetNodeService }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [ASSET_NAVIGATOR_CONFIG]
                }] }] });

class GroupNode extends AssetNode {
    static { this.NAME = 'GroupNode'; }
    /**
     * Creates a new node which shows only groups.
     *
     * @param service The service to use.
     * @param config  The default configuration of the node.
     * @param groupsOnly Set this true, if only groups should be shown.
     * @param selectable Set this true, if it is selectable.
     */
    constructor(service, config = {}) {
        super(service, config);
        this.service = service;
        /**
         * Set this true, if only groups should be shown.
         */
        this.groupsOnly = false;
        /**
         * Set this true, if it groups are also selectable.
         */
        this.groupsSelectable = false;
        /**
         * Devices with children can be selected to show their child devices.
         */
        this.showChildDevices = false;
        /**
         * Group node children (subentries).
         */
        this.children = [];
        this.groupsOnly = config.groupsOnly || false;
        this.groupsSelectable = config.groupsSelectable || false;
        this.showChildDevices = config.showChildDevices || false;
    }
    /**
     * Adds the MO as a child node.
     * @param mo ManagedObject
     */
    addManagedObject(mo) {
        const { childAdditions } = this.mo;
        if (!this.isChildAddition(childAdditions, mo)) {
            this.add(this.service.createChildNode({
                mo,
                groupsOnly: this.groupsOnly,
                groupsSelectable: this.groupsSelectable,
                showChildDevices: this.showChildDevices
            }));
        }
    }
    /**
     * Counts the number of children for the current node (with the exception of the UnassignedDevicesNode).
     */
    countChildren() {
        return this.children.filter(value => value.toString() !== 'UnassignedDevicesNode').length;
    }
    /**
     * Removes all child nodes except the UnassignedDevicesNode.
     */
    empty() {
        this.children = this.children.filter(value => value.toString() === 'UnassignedDevicesNode');
    }
    fetch() {
        const isRoot = this.root;
        const isDevice = this.mo.c8y_IsDevice;
        return isRoot
            ? this.service.getRootNodes()
            : isDevice
                ? this.service.getDeviceChildren(this.mo.id, {}, this.filterQuery$.value, this.showChildDevices)
                : this.service.getGroupItems(this.mo.id, this.groupsOnly
                    ? {
                        query: `$filter=(has(${GroupFragment.groupFragmentType}))`
                    }
                    : {}, this.showChildDevices, this.filterQuery$.value);
    }
    toString() {
        return GroupNode.NAME;
    }
    isGroup() {
        return this.mo && this.service.isGroup(this.mo);
    }
}

class GroupNodeService extends AssetNodeService {
    constructor() {
        super(...arguments);
        this.PAGE_SIZE = 5;
    }
    createGroupNode(config) {
        return new GroupNode(this, config);
    }
    createChildNode(config) {
        return this.createGroupNode(config);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: GroupNodeService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: GroupNodeService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: GroupNodeService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

class AssetSelectorService extends AssetNodeService {
    constructor() {
        super(...arguments);
        /**
         * Function which will check if the node is selectable.
         */
        this.isNodeSelectableFn = true;
    }
    /**
     * Sets the function that will decide if the node is selectable.
     * @param fn A boolean or a function that will decide if the node is selectable.
     */
    setIsNodeSelectable(fn) {
        this.isNodeSelectableFn = fn;
    }
    /**
     * Checks if the node is selectable.
     * @param node The node to check.
     */
    isNodeSelectable(node) {
        if (typeof this.isNodeSelectableFn === 'boolean') {
            return this.isNodeSelectableFn;
        }
        else if (typeof this.isNodeSelectableFn === 'function') {
            return this.isNodeSelectableFn(node);
        }
        return true;
    }
    /**
     * Simplifies the object model based on the selected mode.
     * @param obj The selected asset.
     * @param mode The mode which will decide what type of model will be returned.
     */
    normalizeValue(obj, modelMode) {
        return this.simplifyModel(this.normalizeModelValue(obj), modelMode);
    }
    simplifyModel(model, mode) {
        const mapModel = model => {
            const { id, name, c8y_DeviceQueryString } = model;
            return { id, name, ...(c8y_DeviceQueryString ? { c8y_DeviceQueryString } : {}) };
        };
        if (mode === 'full') {
            return model;
        }
        if (!isArray(model)) {
            return mapModel(model);
        }
        return model.map(mapModel);
    }
    /**
     * Returns the index of the currently selected item.
     * @param selected All selected items
     * @param selectedMo The new selected item-
     * @returns An index, or -1 if not found.
     */
    getIndexOfSelected(selected, selectedMo) {
        return selected.findIndex(mo => mo.id === selectedMo.id);
    }
    normalizeModelValue(value) {
        if (isNumber(value) || isString(value)) {
            return [{ id: value }];
        }
        if (isArray(value)) {
            return value;
        }
        if (isObject(value)) {
            return [value];
        }
        return [];
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }] });

class AssetSelectorNodeComponent {
    /**
     * @ignore
     */
    get expandTitle() {
        return !this.node.open ? gettext('Expand') : gettext('Collapse');
    }
    /**
     * @ignore only di
     */
    constructor(translateService, cd, assetSelectorService) {
        this.translateService = translateService;
        this.cd = cd;
        this.assetSelectorService = assetSelectorService;
        /**
         * All preselected items.
         */
        this.preselected = [];
        /**
         * Should the path be shown.
         */
        this.showPath = false;
        /**
         * Can the user select multiple assets.
         */
        this.multi = false;
        /**
         * The current path to the node.
         */
        this.view = 'tree';
        this.disabled = false;
        /**
         * Event, which indicates whether the loading of the node has completed.
         */
        this.isLoadingState = new EventEmitter();
        /**
         * Event that emits when a node is selected.
         */
        this.onSelect = new EventEmitter();
        /**
         * Event that emits when a node is deselected.
         */
        this.onDeselect = new EventEmitter();
        /**
         * @ignore
         */
        this.level = 0;
        /**
         * @ignore
         */
        this.unsubscribe$ = new Subject();
        this.isNodeSelectable = true;
        /** sets the `btn-pending` class in the load more button */
        this.isLoading = false;
    }
    /**
     * @ignore
     */
    async ngOnInit() {
        this.isNodeSelectable = this.assetSelectorService.isNodeSelectable(this.node);
        this.breadcrumb = this.node.label;
        this.setupBreadcrumbsAndLevel(this.node);
        if (this.node instanceof GroupNode) {
            this.node.hookEvents();
        }
        // open on startup
        if (this.node.root) {
            this.click();
        }
        // used for loading and to trigger change detection when the node is no longer loading.
        if (this.node.events) {
            this.node.events
                .pipe(takeUntil(this.unsubscribe$), filter((a) => a === AssetsNavigatorAction.LOADING_DONE))
                .subscribe(() => {
                this.isLoadingState.emit(false);
                this.cd.markForCheck();
            });
        }
    }
    /**
     * Opens a node.
     */
    click() {
        this.node.open = !this.node.open;
        this.node.click({ open: this.node.open });
    }
    setupBreadcrumbsAndLevel(node) {
        if (node.parents && node.parents.length) {
            const parent = node.parents[0];
            this.breadcrumb =
                this.translateService.instant(parent.label) +
                    ' > ' +
                    this.translateService.instant(this.breadcrumb);
            this.level++;
            this.setupBreadcrumbsAndLevel(parent);
        }
    }
    /**
     * Selects the node and emits a change on the parent component.
     * @param node The node to select.
     */
    selected(node) {
        if (node.mo) {
            this.updateSelection(node.mo);
            return;
        }
        this.click();
    }
    /**
     * Handles clicks on a item in Miller View.
     * @param node The node that was clicked.
     */
    millerViewClick(node) {
        node.breadcrumb = this.breadcrumb;
        if (!this.handleNextMillerViewColumn) {
            return;
        }
        const shouldHandleDefault = this.handleNextMillerViewColumn(node, this.index);
        if (shouldHandleDefault) {
            this.selected(node);
        }
    }
    /**
     * @ignore
     */
    ngOnDestroy() {
        this.unsubscribe$.next();
        this.unsubscribe$.complete();
    }
    isSelected() {
        if (!this.node.mo) {
            return false;
        }
        return this.assetSelectorService.getIndexOfSelected(this.preselected, this.node.mo) > -1;
    }
    isActive() {
        if (this.active && this.node.mo) {
            return this.active.mo?.id === this.node.mo.id;
        }
        return false;
    }
    updateSelection(selectedMo) {
        if (!this.multi) {
            this.onDeselect.emit({ deselectMode: 'all', mo: selectedMo });
            return;
        }
        if (this.isSelected()) {
            this.onDeselect.emit({ deselectMode: 'single', mo: selectedMo });
            return;
        }
        this.onSelect.emit(selectedMo);
        this.cd.markForCheck();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorNodeComponent, deps: [{ token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: AssetSelectorService }], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: AssetSelectorNodeComponent, isStandalone: true, selector: "c8y-asset-selector-node", inputs: { node: "node", rootNode: "rootNode", preselected: "preselected", showPath: "showPath", multi: "multi", view: "view", index: "index", active: "active", handleNextMillerViewColumn: "handleNextMillerViewColumn", disabled: "disabled" }, outputs: { isLoadingState: "isLoadingState", onSelect: "onSelect", onDeselect: "onDeselect" }, ngImport: i0, template: "<!-- Hierarchy tree -->\n<div\n  class=\"c8y-asset-selector__item\"\n  [ngStyle]=\"{\n    'margin-left': level > 1 ? 16 + 'px' : '0'\n  }\"\n  *ngIf=\"view === 'tree'\"\n  [attr.role]=\"view === 'tree' ? 'tree' : 'list'\"\n  [ngClass]=\"{\n    'c8y-asset-selector__item--more': node?.icon === 'plus',\n    'c8y-asset-selector__item--start': level === 0\n  }\"\n>\n  <div\n    class=\"c8y-asset-selector__node\"\n    title=\"{{ breadcrumb | translate }}\"\n    *ngIf=\"node && !node.root && !node.hidden\"\n    [attr.role]=\"view === 'tree' ? 'treeitem' : 'listitem'\"\n    [ngClass]=\"{ 'c8y-asset-selector__node--open': node?.open }\"\n  >\n    <!-- expand button -->\n    <div class=\"c8y-asset-selector__node__btn-spacer\">\n      <button\n        class=\"collapse-btn btn-dot\"\n        [title]=\"expandTitle | translate\"\n        [attr.aria-expanded]=\"node.open\"\n        (click)=\"click()\"\n        *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      >\n        <i c8yIcon=\"angle-right\"></i>\n      </button>\n    </div>\n    <div\n      class=\"d-flex a-i-center p-t-4 p-b-4\"\n      *ngIf=\"node.toString() !== 'LoadMoreNode' && isNodeSelectable\"\n    >\n      <label [ngClass]=\"{ 'c8y-checkbox': multi, 'c8y-radio': !multi }\">\n        <input\n          id=\"nodeLabel\"\n          [type]=\"multi ? 'checkbox' : 'radio'\"\n          (change)=\"selected(node)\"\n          [checked]=\"isSelected()\"\n          [disabled]=\"disabled || !node.groupsSelectable && node.isGroup()\"\n        />\n        <span></span>\n        <span\n          class=\"sr-only\"\n          for=\"nodeLabel\"\n          translate\n        >\n          Node label\n        </span>\n      </label>\n    </div>\n\n    <!-- group button -->\n    <button\n      class=\"c8y-asset-selector__btn text-truncate\"\n      [attr.aria-expanded]=\"!node.open\"\n      *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      (click)=\"click()\"\n    >\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Smart group' | translate\"\n        *ngIf=\"node.icon === 'c8y-group-smart'\"\n      ></i>\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Group' | translate\"\n        *ngIf=\"node.icon !== 'c8y-group-smart'\"\n      ></i>\n      <span title=\"{{ breadcrumb }}\">\n        {{ node.translateLabel ? (node.label | translate) : node.label }}\n        <!-- use just for search results to display the path -->\n        <p\n          class=\"text-truncate\"\n          *ngIf=\"showPath\"\n        >\n          <small\n            class=\"text-muted\"\n            title=\"{{ breadcrumb }}\"\n          >\n            <em>{{ breadcrumb }}</em>\n          </small>\n        </p>\n        <!-- up to here -->\n      </span>\n    </button>\n    <!-- not a group button -->\n    <button\n      class=\"flex-grow\"\n      title=\"{{ breadcrumb }}\"\n      type=\"button\"\n      *ngIf=\"!node.isGroup() && !node.hasChildDevices()\"\n      [ngClass]=\"{\n        'btn btn-default btn-sm m-b-8 m-r-8 d-flex j-c-center': node.icon === 'plus',\n        'c8y-asset-selector__btn text-truncate': node.icon != 'plus'\n      }\"\n      (click)=\"selected(node)\"\n    >\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Smart group' | translate\"\n        *ngIf=\"node.icon === 'c8y-group-smart'\"\n      ></i>\n      <i\n        class=\"c8y-icon m-r-4\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Group' | translate\"\n        *ngIf=\"node.icon !== 'c8y-group-smart'\"\n        [ngClass]=\"{ 'c8y-icon-duocolor text-16 ': node.icon != 'plus' }\"\n      ></i>\n      <span title=\"{{ breadcrumb }}\">\n        {{ node.translateLabel ? (node.label | translate) : node.label }}\n        <!-- use just for search results to display the path -->\n        <p\n          class=\"text-truncate text-muted small\"\n          *ngIf=\"showPath\"\n        >\n          <em>{{ breadcrumb }}</em>\n        </p>\n        <!-- up to here -->\n      </span>\n    </button>\n  </div>\n  <div\n    class=\"collapse\"\n    *ngIf=\"node.countChildren()\"\n    [collapse]=\"!node.open\"\n    [isAnimated]=\"true\"\n    [attr.role]=\"'group'\"\n  >\n    <c8y-asset-selector-node\n      *ngFor=\"let childNode of node.children\"\n      [node]=\"childNode\"\n      [preselected]=\"preselected || []\"\n      [disabled]=\"disabled\"\n      [multi]=\"multi\"\n      [active]=\"active\"\n      [attr.role]=\"view === 'tree' ? 'treeitem' : 'listitem'\"\n      (onSelect)=\"onSelect.emit($event)\"\n      (onDeselect)=\"onDeselect.emit($event)\"\n    ></c8y-asset-selector-node>\n  </div>\n</div>\n\n<!-- Miller columns -->\n<div *ngIf=\"view === 'miller'\">\n  <div\n    class=\"miller-column__item bg-inherit\"\n    title=\"{{ breadcrumb | translate }}\"\n    *ngIf=\"node && !node.root && !node.hidden && node !== rootNode\"\n    [ngClass]=\"{\n      active: isActive(),\n      'miller-column__item--more': node.toString() === 'LoadMoreNode'\n    }\"\n  >\n    <div\n      class=\"m-l-4 m-r-4 miller-column__item__checkbox\"\n      *ngIf=\"node.toString() !== 'LoadMoreNode'\"\n    >\n      <label [ngClass]=\"{ 'c8y-radio': !multi, 'c8y-checkbox': multi }\">\n        <input\n          id=\"nodeLabel2\"\n          [type]=\"multi ? 'checkbox' : 'radio'\"\n          (change)=\"selected(node)\"\n          [checked]=\"isSelected()\"\n          [disabled]=\"disabled || !node.groupsSelectable && node.isGroup()\"\n        />\n        <span></span>\n        <span\n          class=\"sr-only\"\n          for=\"nodeLabel2\"\n          translate\n        >\n          Node label\n        </span>\n      </label>\n    </div>\n\n    <button\n      title=\"{{ breadcrumb | translate }}\"\n      type=\"button\"\n      [ngClass]=\"{\n        'btn btn-default btn-sm d-flex flex-grow j-c-center m-l-16 m-r-16 m-b-4 m-t-4':\n          node.toString() === 'LoadMoreNode',\n        'miller-column__item__btn': node.toString() !== 'LoadMoreNode',\n        'btn-pending': node.loading && node.toString() === 'LoadMoreNode'\n      }\"\n      (click)=\"millerViewClick(node)\"\n    >\n      <i\n        class=\"c8y-icon m-r-4\"\n        [c8yIcon]=\"node.icon\"\n        [ngClass]=\"{ 'c8y-icon-duocolor text-16': node.toString() !== 'LoadMoreNode' }\"\n      ></i>\n      <div class=\"text-left text-truncate\">\n        <p\n          class=\"text-truncate\"\n          title=\"{{ node.translateLabel ? (node.label | translate) : node.label }}\"\n        >\n          {{ node.translateLabel ? (node.label | translate) : node.label }}\n        </p>\n        <!-- use just for search results to display the path -->\n        <small\n          class=\"text-muted text-truncate\"\n          title=\"{{ breadcrumb }}\"\n          *ngIf=\"showPath\"\n        >\n          <em>{{ breadcrumb }}</em>\n        </small>\n        <!-- up to here -->\n      </div>\n      <span\n        class=\"p-l-4 m-l-auto\"\n        *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      >\n        <i c8yIcon=\"angle-right\"></i>\n      </span>\n    </button>\n  </div>\n\n  <div\n    role=\"list\"\n    *ngIf=\"node\"\n    [ngClass]=\"{ hidden: node !== rootNode }\"\n  >\n    <c8y-asset-selector-node\n      role=\"listitem\"\n      *ngFor=\"let childNode of node.children\"\n      [node]=\"childNode\"\n      [rootNode]=\"rootNode\"\n      [preselected]=\"preselected || []\"\n      [multi]=\"multi\"\n      [view]=\"view\"\n      [index]=\"index\"\n      [active]=\"active\"\n      [disabled]=\"disabled\"\n      [handleNextMillerViewColumn]=\"handleNextMillerViewColumn\"\n      (onSelect)=\"onSelect.emit($event)\"\n      (onDeselect)=\"onDeselect.emit($event)\"\n    ></c8y-asset-selector-node>\n  </div>\n</div>\n", dependencies: [{ kind: "component", type: AssetSelectorNodeComponent, selector: "c8y-asset-selector-node", inputs: ["node", "rootNode", "preselected", "showPath", "multi", "view", "index", "active", "handleNextMillerViewColumn", "disabled"], outputs: ["isLoadingState", "onSelect", "onDeselect"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorNodeComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-asset-selector-node', imports: [
                        NgIf,
                        NgStyle,
                        NgClass,
                        IconDirective,
                        C8yTranslateDirective,
                        CollapseDirective,
                        NgFor,
                        C8yTranslatePipe
                    ], template: "<!-- Hierarchy tree -->\n<div\n  class=\"c8y-asset-selector__item\"\n  [ngStyle]=\"{\n    'margin-left': level > 1 ? 16 + 'px' : '0'\n  }\"\n  *ngIf=\"view === 'tree'\"\n  [attr.role]=\"view === 'tree' ? 'tree' : 'list'\"\n  [ngClass]=\"{\n    'c8y-asset-selector__item--more': node?.icon === 'plus',\n    'c8y-asset-selector__item--start': level === 0\n  }\"\n>\n  <div\n    class=\"c8y-asset-selector__node\"\n    title=\"{{ breadcrumb | translate }}\"\n    *ngIf=\"node && !node.root && !node.hidden\"\n    [attr.role]=\"view === 'tree' ? 'treeitem' : 'listitem'\"\n    [ngClass]=\"{ 'c8y-asset-selector__node--open': node?.open }\"\n  >\n    <!-- expand button -->\n    <div class=\"c8y-asset-selector__node__btn-spacer\">\n      <button\n        class=\"collapse-btn btn-dot\"\n        [title]=\"expandTitle | translate\"\n        [attr.aria-expanded]=\"node.open\"\n        (click)=\"click()\"\n        *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      >\n        <i c8yIcon=\"angle-right\"></i>\n      </button>\n    </div>\n    <div\n      class=\"d-flex a-i-center p-t-4 p-b-4\"\n      *ngIf=\"node.toString() !== 'LoadMoreNode' && isNodeSelectable\"\n    >\n      <label [ngClass]=\"{ 'c8y-checkbox': multi, 'c8y-radio': !multi }\">\n        <input\n          id=\"nodeLabel\"\n          [type]=\"multi ? 'checkbox' : 'radio'\"\n          (change)=\"selected(node)\"\n          [checked]=\"isSelected()\"\n          [disabled]=\"disabled || !node.groupsSelectable && node.isGroup()\"\n        />\n        <span></span>\n        <span\n          class=\"sr-only\"\n          for=\"nodeLabel\"\n          translate\n        >\n          Node label\n        </span>\n      </label>\n    </div>\n\n    <!-- group button -->\n    <button\n      class=\"c8y-asset-selector__btn text-truncate\"\n      [attr.aria-expanded]=\"!node.open\"\n      *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      (click)=\"click()\"\n    >\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Smart group' | translate\"\n        *ngIf=\"node.icon === 'c8y-group-smart'\"\n      ></i>\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Group' | translate\"\n        *ngIf=\"node.icon !== 'c8y-group-smart'\"\n      ></i>\n      <span title=\"{{ breadcrumb }}\">\n        {{ node.translateLabel ? (node.label | translate) : node.label }}\n        <!-- use just for search results to display the path -->\n        <p\n          class=\"text-truncate\"\n          *ngIf=\"showPath\"\n        >\n          <small\n            class=\"text-muted\"\n            title=\"{{ breadcrumb }}\"\n          >\n            <em>{{ breadcrumb }}</em>\n          </small>\n        </p>\n        <!-- up to here -->\n      </span>\n    </button>\n    <!-- not a group button -->\n    <button\n      class=\"flex-grow\"\n      title=\"{{ breadcrumb }}\"\n      type=\"button\"\n      *ngIf=\"!node.isGroup() && !node.hasChildDevices()\"\n      [ngClass]=\"{\n        'btn btn-default btn-sm m-b-8 m-r-8 d-flex j-c-center': node.icon === 'plus',\n        'c8y-asset-selector__btn text-truncate': node.icon != 'plus'\n      }\"\n      (click)=\"selected(node)\"\n    >\n      <i\n        class=\"c8y-icon c8y-icon-duocolor m-r-4 text-16\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Smart group' | translate\"\n        *ngIf=\"node.icon === 'c8y-group-smart'\"\n      ></i>\n      <i\n        class=\"c8y-icon m-r-4\"\n        [c8yIcon]=\"node.icon\"\n        [title]=\"'Group' | translate\"\n        *ngIf=\"node.icon !== 'c8y-group-smart'\"\n        [ngClass]=\"{ 'c8y-icon-duocolor text-16 ': node.icon != 'plus' }\"\n      ></i>\n      <span title=\"{{ breadcrumb }}\">\n        {{ node.translateLabel ? (node.label | translate) : node.label }}\n        <!-- use just for search results to display the path -->\n        <p\n          class=\"text-truncate text-muted small\"\n          *ngIf=\"showPath\"\n        >\n          <em>{{ breadcrumb }}</em>\n        </p>\n        <!-- up to here -->\n      </span>\n    </button>\n  </div>\n  <div\n    class=\"collapse\"\n    *ngIf=\"node.countChildren()\"\n    [collapse]=\"!node.open\"\n    [isAnimated]=\"true\"\n    [attr.role]=\"'group'\"\n  >\n    <c8y-asset-selector-node\n      *ngFor=\"let childNode of node.children\"\n      [node]=\"childNode\"\n      [preselected]=\"preselected || []\"\n      [disabled]=\"disabled\"\n      [multi]=\"multi\"\n      [active]=\"active\"\n      [attr.role]=\"view === 'tree' ? 'treeitem' : 'listitem'\"\n      (onSelect)=\"onSelect.emit($event)\"\n      (onDeselect)=\"onDeselect.emit($event)\"\n    ></c8y-asset-selector-node>\n  </div>\n</div>\n\n<!-- Miller columns -->\n<div *ngIf=\"view === 'miller'\">\n  <div\n    class=\"miller-column__item bg-inherit\"\n    title=\"{{ breadcrumb | translate }}\"\n    *ngIf=\"node && !node.root && !node.hidden && node !== rootNode\"\n    [ngClass]=\"{\n      active: isActive(),\n      'miller-column__item--more': node.toString() === 'LoadMoreNode'\n    }\"\n  >\n    <div\n      class=\"m-l-4 m-r-4 miller-column__item__checkbox\"\n      *ngIf=\"node.toString() !== 'LoadMoreNode'\"\n    >\n      <label [ngClass]=\"{ 'c8y-radio': !multi, 'c8y-checkbox': multi }\">\n        <input\n          id=\"nodeLabel2\"\n          [type]=\"multi ? 'checkbox' : 'radio'\"\n          (change)=\"selected(node)\"\n          [checked]=\"isSelected()\"\n          [disabled]=\"disabled || !node.groupsSelectable && node.isGroup()\"\n        />\n        <span></span>\n        <span\n          class=\"sr-only\"\n          for=\"nodeLabel2\"\n          translate\n        >\n          Node label\n        </span>\n      </label>\n    </div>\n\n    <button\n      title=\"{{ breadcrumb | translate }}\"\n      type=\"button\"\n      [ngClass]=\"{\n        'btn btn-default btn-sm d-flex flex-grow j-c-center m-l-16 m-r-16 m-b-4 m-t-4':\n          node.toString() === 'LoadMoreNode',\n        'miller-column__item__btn': node.toString() !== 'LoadMoreNode',\n        'btn-pending': node.loading && node.toString() === 'LoadMoreNode'\n      }\"\n      (click)=\"millerViewClick(node)\"\n    >\n      <i\n        class=\"c8y-icon m-r-4\"\n        [c8yIcon]=\"node.icon\"\n        [ngClass]=\"{ 'c8y-icon-duocolor text-16': node.toString() !== 'LoadMoreNode' }\"\n      ></i>\n      <div class=\"text-left text-truncate\">\n        <p\n          class=\"text-truncate\"\n          title=\"{{ node.translateLabel ? (node.label | translate) : node.label }}\"\n        >\n          {{ node.translateLabel ? (node.label | translate) : node.label }}\n        </p>\n        <!-- use just for search results to display the path -->\n        <small\n          class=\"text-muted text-truncate\"\n          title=\"{{ breadcrumb }}\"\n          *ngIf=\"showPath\"\n        >\n          <em>{{ breadcrumb }}</em>\n        </small>\n        <!-- up to here -->\n      </div>\n      <span\n        class=\"p-l-4 m-l-auto\"\n        *ngIf=\"node.isGroup() || node.hasChildDevices()\"\n      >\n        <i c8yIcon=\"angle-right\"></i>\n      </span>\n    </button>\n  </div>\n\n  <div\n    role=\"list\"\n    *ngIf=\"node\"\n    [ngClass]=\"{ hidden: node !== rootNode }\"\n  >\n    <c8y-asset-selector-node\n      role=\"listitem\"\n      *ngFor=\"let childNode of node.children\"\n      [node]=\"childNode\"\n      [rootNode]=\"rootNode\"\n      [preselected]=\"preselected || []\"\n      [multi]=\"multi\"\n      [view]=\"view\"\n      [index]=\"index\"\n      [active]=\"active\"\n      [disabled]=\"disabled\"\n      [handleNextMillerViewColumn]=\"handleNextMillerViewColumn\"\n      (onSelect)=\"onSelect.emit($event)\"\n      (onDeselect)=\"onDeselect.emit($event)\"\n    ></c8y-asset-selector-node>\n  </div>\n</div>\n" }]
        }], ctorParameters: () => [{ type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: AssetSelectorService }], propDecorators: { node: [{
                type: Input
            }], rootNode: [{
                type: Input
            }], preselected: [{
                type: Input
            }], showPath: [{
                type: Input
            }], multi: [{
                type: Input
            }], view: [{
                type: Input
            }], index: [{
                type: Input
            }], active: [{
                type: Input
            }], handleNextMillerViewColumn: [{
                type: Input
            }], disabled: [{
                type: Input
            }], isLoadingState: [{
                type: Output
            }], onSelect: [{
                type: Output
            }], onDeselect: [{
                type: Output
            }] } });

class AssetSelectorBase {
    constructor(groupNodeService, inventory, assetSelectorService) {
        this.groupNodeService = groupNodeService;
        this.inventory = inventory;
        this.assetSelectorService = assetSelectorService;
        /**
         * Config object containing all options for the asset selector.
         */
        this.config = {};
        /**
         * The loading state of the current node.
         */
        this.isLoading = false;
        /**
         * Emit the selected asset or assets.
         */
        this.onSelected = new EventEmitter();
        /**
         * Emits if the currently selected asset is cleared.
         */
        this.onClearSelected = new EventEmitter();
    }
    /**
     * @ignore
     */
    validate(control) {
        if (this.config.required && (!control.value || control.value.length === 0)) {
            return { required: true };
        }
        return null;
    }
    /**
     * @ignore
     */
    writeValue(obj) {
        this.selected = this.assetSelectorService.normalizeValue(obj, this.config.modelMode);
    }
    /**
     * @ignore
     */
    registerOnChange(fn) {
        this.onChange = fn;
    }
    /**
     * @ignore
     */
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    /**
     * @ignore
     */
    doBlur() {
        if (this.onTouched) {
            this.onTouched();
        }
    }
    /**
     * Deselects all expect the given one.
     * @param selectedMo The selected asset (optional, if empty -> just deselect all).
     */
    deselectAll(selectedMo) {
        this.selected = [];
        if (selectedMo) {
            this.selected = [this.assetSelectorService.simplifyModel(selectedMo, this.config.modelMode)];
        }
        this.emitChange(this.selected, selectedMo, !!selectedMo);
        this.doBlur();
    }
    /**
     * Adds an managed object to the selected array and emits the change.
     * @param selectedMo The selected asset.
     */
    select(selectedMo) {
        if (!this.config.multi) {
            this.selected = [];
        }
        const index = this.getIndexOfSelected(this.selected, selectedMo);
        if (index === -1) {
            this.selected.push(this.assetSelectorService.simplifyModel(selectedMo, this.config.modelMode));
        }
        this.emitChange(this.selected, selectedMo, true);
        this.doBlur();
    }
    /**
     * Removes a managed object from the selected array and emits the change.
     * @param selectedMo The selected asset.
     */
    deselect(selectedMo) {
        const index = this.getIndexOfSelected(this.selected, selectedMo);
        if (index > -1) {
            this.selected.splice(index, 1);
        }
        this.emitChange(this.selected, selectedMo, false);
        this.doBlur();
    }
    /**
     * Returns the index of the currently selected item.
     * @param selected All selected items
     * @param selectedMo The new selected item-
     * @returns An index, or -1 if not found.
     */
    getIndexOfSelected(selected, selectedMo) {
        return selected.findIndex(mo => mo.id === selectedMo.id);
    }
    emitChange(selected, item, isSelected) {
        const changes = this.config.multi ? selected : selected[0];
        if (this.onChange) {
            this.onChange(changes);
        }
        this.onSelected.emit({ items: changes, change: { item, isSelected } });
    }
    createDefaultRootNode() {
        return this.groupNodeService.createGroupNode({
            root: true,
            groupsSelectable: this.config.groupsSelectable,
            groupsOnly: this.config.groupsOnly,
            showChildDevices: this.config.showChildDevices
        });
    }
    async createManagedObjectRootNode(asset) {
        if (asset.id) {
            return this.groupNodeService.createGroupNode({
                mo: asset,
                groupsSelectable: this.config.groupsSelectable,
                groupsOnly: this.config.groupsOnly,
                showChildDevices: this.config.showChildDevices
            });
        }
        const { data: mo } = await this.inventory.detail(asset);
        return this.groupNodeService.createGroupNode({
            mo,
            groupsSelectable: this.config.groupsSelectable,
            groupsOnly: this.config.groupsOnly,
            showChildDevices: this.config.showChildDevices
        });
    }
}

const CONFIG_OPTIONS_DEFAULT = {
    label: gettext('Asset selection'),
    multi: false,
    showFilter: false,
    columnHeaders: false,
    singleColumn: false,
    modelMode: 'simple',
    groupsOnly: false,
    groupsSelectable: false,
    search: false,
    showUnassignedDevices: false,
    view: 'tree',
    showChildDevices: false,
    showSelected: true
};

class UnassignedDevicesNode extends GroupNode {
    static { this.NAME = 'UnassignedDevicesNode'; }
    constructor(service, config = {}) {
        super(service, config);
        this.service = service;
        this.label = gettext('Unassigned devices');
        this.icon = 'delete-folder';
        this.groupsSelectable = false;
        this.groupsOnly = false;
        this.showChildDevices = false;
        this.mo = {};
        this.priority = Infinity;
        this.showChildDevices = config.showChildDevices || false;
    }
    click(options = {}) {
        this.hookEvents();
        if (options.open) {
            this.events.next(AssetsNavigatorAction.FETCH);
        }
    }
    addManagedObject(mo) {
        this.add(this.service.createChildNode({ mo, showChildDevices: this.showChildDevices }));
    }
    fetch() {
        return this.service.getUnassignedDevices(this.showChildDevices, this.filterQuery$.value);
    }
    isGroup() {
        return true;
    }
    toString() {
        return UnassignedDevicesNode.NAME;
    }
    setLabel() {
        // do nothing, as this item has a hardcoded label.
        // This is only to overwrite the default inherited method.
        this.label = this.label;
    }
    async updateIcon() {
        // do nothing, as this item has a hardcoded label.
        // This is only to overwrite the default inherited method.
        this.icon = this.icon;
    }
}

/**
 * Displays a hierarchical tree selector for assets.
 */
class AssetSelectorComponent extends AssetSelectorBase {
    /**
     * @ignore only DI
     */
    constructor(groupNodeService, inventory, assetSelectorService, cd) {
        super(groupNodeService, inventory, assetSelectorService);
        this.groupNodeService = groupNodeService;
        this.inventory = inventory;
        this.assetSelectorService = assetSelectorService;
        this.cd = cd;
        /**
         * Config object containing all options for the asset selector.
         */
        this.config = {};
        /**
         * Used only for miller-view, displays the column level for the current node.
         * E.g if the index is one, this will be second column.
         */
        this.index = 0;
        /**
         * The asset to use as root.
         */
        this.asset = undefined;
        /**
         * The selected device.
         */
        this.selectedDevice = undefined;
        /**
         * An array of predefined nodes.
         */
        this.selected = [];
        /**
         * All currently selected assets.
         */
        this.selectedItems = [];
        this.container = 'body';
        this.isNodeSelectable = true;
        this.disabled = false;
        /**
         * Emits if one item was selected (all currently selected nodes).
         */
        this.onSelected = new EventEmitter();
        /**
         * Emits if the currently selected asset is cleared.
         */
        this.onClearSelected = new EventEmitter();
        /**
         * Used only for miller view. Emit the selected node and use it as a 'rootNode' for the new column.
         */
        this.onRowSelected = new EventEmitter();
        /**
         * Emits the current loading state of the node.
         */
        this.onLoad = new EventEmitter();
        /**
         * The current filter applied.
         */
        this.filterText = '';
        /**
         * Displays the global search at all times if the miller view is used
         * on root group level.
         */
        this.root = false;
        /**
         * @ignore
         */
        this.unsubscribe$ = new Subject();
        /**
         * Returns the current header hight in pixel.
         */
        this.getHeaderHeight$ = new BehaviorSubject(0);
        this.ASSET_CONST = gettext('Asset {{ assetId }}');
    }
    /**
     * Checks when a node was selected, if a new column needs to be added.
     * @param node The node that was clicked.
     * @param index The current index of this node.
     * @returns True if the click was handled and a new column was added.
     */
    handleNextMillerViewColumn(node, index) {
        const nodeCopy = Object.create(node);
        const isSameAsRoot = this.rootNode === node;
        const isUnassignedDevicesNode = node.toString() === UnassignedDevicesNode.NAME;
        const isNewColumnNeeded = !isSameAsRoot && (node.isGroup() || isUnassignedDevicesNode || node.hasChildDevices());
        if (isNewColumnNeeded) {
            this.onRowSelected.emit({ nodeCopy, index });
            return false;
        }
        return true;
    }
    /**
     * @ignore
     */
    async ngOnInit() {
        this.assetSelectorService.setIsNodeSelectable(this.isNodeSelectable);
        if (this.index === 0) {
            this.config = { ...CONFIG_OPTIONS_DEFAULT, ...this.config };
        }
        const shouldResetNameFilter = this.rootNode && this.rootNode.mo && this.config.view === 'miller';
        if (shouldResetNameFilter) {
            await this.applyFilter('');
        }
        const isOnlyAssetNodeDefined = this.asset;
        if (isOnlyAssetNodeDefined) {
            this.rootNode = await this.createManagedObjectRootNode(this.asset);
        }
        const isAnyNodeDefined = !this.rootNode && (!this.asset || Object.keys(this.asset).length === 0);
        if (isAnyNodeDefined) {
            this.rootNode = this.createDefaultRootNode();
        }
        if (this.config.showUnassignedDevices &&
            (!this.asset || Object.keys(this.asset).length === 0) &&
            this.rootNode.root) {
            this.rootNode.add(new UnassignedDevicesNode(this.groupNodeService, {
                showChildDevices: this.config.showChildDevices
            }));
        }
        this.root = this.rootNode.root;
    }
    async ngOnChanges(changes) {
        if (changes.isNodeSelectable) {
            this.assetSelectorService.setIsNodeSelectable(this.isNodeSelectable);
        }
        if (changes.asset &&
            changes.asset.currentValue &&
            Object.keys(changes.asset.currentValue).length !== 0) {
            this.rootNode = await this.createManagedObjectRootNode(changes.asset.currentValue);
        }
    }
    /**
     * @ignore
     */
    ngAfterViewInit() {
        this.updateHeaderHeight();
    }
    /**
     * Changes the current root to one level back.
     */
    back() {
        const index = this.index - 1;
        const nodeCopy = Object.create(this.rootNode.parents[0]);
        this.onRowSelected.emit({ nodeCopy, index });
    }
    /**
     * Clears the current filter.
     */
    clearFilters() {
        this.filterText = '';
        this.applyFilter('');
    }
    /**
     * Clears the currently selected device.
     */
    clearSelectedDevices() {
        this.selectedDevice = undefined;
        this.applyFilter('');
        this.onClearSelected.emit();
    }
    isGroupSelected() {
        if (this.selected.some(selectedNode => selectedNode.id === this.rootNode.mo?.id)) {
            return true;
        }
        return false;
    }
    /**
     * @param nameFilter The name filter to apply.
     * @param moId The managed object ID to filter by.
     * @param showUnassignedDevices If true, it will show unassigned devices.
     * @return The filter query to apply.
     * @description
     * This method builds a filter query based on the provided name filter and managed object ID.
     */
    groupNameFilter(nameFilter, moId, showUnassignedDevices) {
        if (nameFilter) {
            const filterByName = showUnassignedDevices
                ? {
                    __filter: {
                        name: nameFilter
                    },
                    __orderby: [{ name: 1 }]
                }
                : {
                    __filter: {
                        name: nameFilter,
                        __bygroupid: moId
                    }
                };
            return this.assetSelectorService.queriesUtil.buildQuery(filterByName);
        }
        const defaultFilter = {
            __filter: {
                __bygroupid: moId
            },
            __orderby: [{ name: 1 }]
        };
        return this.assetSelectorService.queriesUtil.buildQuery(defaultFilter);
    }
    /**
     * Applies a filter.
     * @param filter The filter to apply.
     */
    async applyFilter(filter) {
        this.rootNode.filterQuery$.next(this.groupNameFilter(filter, this.rootNode.mo.id, this.isUnassignedDevicesNode() ? true : false));
        this.rootNode.refresh();
        this.onLoad.emit({
            loading: true,
            filterText: this.filterText,
            index: this.index,
            selectedDevice: this.selectedDevice
        });
        this.cd.detectChanges();
    }
    /**
     * Check if the UnassignedDevice node is the rootNode.
     */
    isUnassignedDevicesNode() {
        return this.rootNode.toString() === UnassignedDevicesNode.NAME;
    }
    /**
     * Called when the user resets the search result.
     */
    onSearchResultReset(selectedItem) {
        if (selectedItem) {
            this.rootNode = this.createDefaultRootNode();
            if (this.config.showUnassignedDevices) {
                this.rootNode.add(new UnassignedDevicesNode(this.groupNodeService, {
                    showChildDevices: this.config.showChildDevices
                }));
            }
            if (this.config.view === 'miller') {
                this.onRowSelected.emit({ root: true });
            }
            this.rootNode.click({ open: true });
            this.rootNode.nodesFetched
                .pipe(tap(() => this.cd.detectChanges()), takeUntil(this.unsubscribe$))
                .subscribe();
            if (this.config.view === 'tree') {
                this.rootNode.open = true;
            }
        }
    }
    /**
     * Called if the user clicks on a search result.
     */
    async onSearchResultClick(mo) {
        const canSelectNode = this.config.groupsSelectable || !this.groupNodeService.isGroup(mo);
        if (this.config.view === 'miller') {
            this.rootNode = await this.createManagedObjectRootNode(mo);
            this.rootNode.click({ open: true });
            const nodeCopy = Object.create(this.rootNode);
            this.onRowSelected.emit({
                nodeCopy,
                index: 0,
                selectedDevices: this.selectedDevice,
                root: true
            });
        }
        if (!this.config.multi) {
            this.deselectAll();
        }
        if (canSelectNode) {
            this.select(mo);
        }
    }
    onLoading(isLoading) {
        this.onLoad.emit({ loading: isLoading, filterText: this.filterText });
        this.cd.markForCheck();
    }
    onSelect(mo) {
        this.select(mo);
    }
    onDeselect(config) {
        switch (config?.deselectMode) {
            case 'single':
                this.deselect(config.mo);
                this.cd.markForCheck();
                break;
            case 'all':
                this.deselectAll(config.mo);
                break;
        }
    }
    updateHeaderHeight() {
        // An observer is used to detect if the header is visible or not.
        // If the header is not visible, the height is 0 and the observer is
        // used to detect when it becomes visible, to recalculate the height.
        const observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.updateHeaderHeight();
                    observer.unobserve(entry.target);
                }
            });
        });
        requestAnimationFrame(() => {
            if (this.header?.nativeElement) {
                const { clientHeight } = this.header.nativeElement;
                if (clientHeight === 0 && this.isElementOrParentHidden(this.header.nativeElement)) {
                    observer.observe(this.header.nativeElement);
                    return;
                }
                this.getHeaderHeight$.next(clientHeight);
            }
            else {
                this.getHeaderHeight$.next(0);
            }
        });
    }
    isElementOrParentHidden(element) {
        while (element) {
            const style = window.getComputedStyle(element);
            if (style.display === 'none' || style.visibility === 'hidden') {
                return true;
            }
            element = element.parentElement;
        }
        return false;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorComponent, deps: [{ token: GroupNodeService }, { token: i2.InventoryService }, { token: AssetSelectorService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: AssetSelectorComponent, isStandalone: true, selector: "c8y-asset-selector", inputs: { config: "config", active: "active", index: "index", asset: "asset", selectedDevice: "selectedDevice", selected: "selected", rootNode: "rootNode", selectedItems: "selectedItems", container: "container", isNodeSelectable: "isNodeSelectable", disabled: "disabled" }, outputs: { onSelected: "onSelected", onClearSelected: "onClearSelected", onRowSelected: "onRowSelected", onLoad: "onLoad" }, providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                multi: true,
                useExisting: forwardRef(() => AssetSelectorComponent)
            },
            {
                provide: NG_VALIDATORS,
                useExisting: forwardRef(() => AssetSelectorComponent),
                multi: true
            },
            AssetSelectorService
        ], viewQueries: [{ propertyName: "header", first: true, predicate: ["header"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div\n  class=\"bg-inherit p-t-8 p-b-8 separator-bottom p-r-16 p-l-16 sticky-top\"\n  [ngStyle]=\"{ 'z-index': config.singleColumn && config.search ? '30' : '20' }\"\n  #header\n  [ngClass]=\"{\n    'p-absolute miller-column__selection d-flex flex-wrap a-i-center': config.view === 'miller',\n    'miller-column__search': config.search,\n    'miller-column__show-selected': config.showSelected\n  }\"\n  *ngIf=\"\n    !selectedDevice &&\n    index === 0 &&\n    (config.search || config.multi || config.label?.length || config.showSelected)\n  \"\n>\n  <p\n    class=\"text-medium flex-no-shrink a-s-center\"\n    [ngClass]=\"{\n      'm-b-4': config.search && root && config.singleColumn,\n      'm-r-8': !config.singleColumn && !config.search,\n      'fit-w': config.singleColumn\n    }\"\n    *ngIf=\"config.label?.length\"\n  >\n    {{ config.label | translate }}\n  </p>\n  <c8y-search-input\n    [ngStyle]=\"{ 'min-width': !config.singleColumn && config.label.length ? '50%' : 'inherit' }\"\n    *ngIf=\"config.search && root\"\n    (onClick)=\"onSearchResultClick($event)\"\n    (reset)=\"onSearchResultReset($event)\"\n    [mode]=\"'select'\"\n    [container]=\"container\"\n    class=\"input-group-sm\"\n    [groupsOnly]=\"config.groupsOnly\"\n    [ngClass]=\"{\n      'm-l-auto': !config.singleColumn && config.label.length,\n      'fit-w': config.singleColumn || !config.label.length\n    }\"\n  ></c8y-search-input>\n  <div\n    class=\"bg-inherit d-flex gap-4 fit-w\"\n    *ngIf=\"config.showSelected\"\n    [ngClass]=\"{ 'm-t-4': config.search }\"\n  >\n    <span class=\"text-label-small m-r-4 a-s-center l-h-1\">{{ 'Selected' | translate }}</span>\n    <div class=\"flex-grow inner-scroll d-flex gap-4 a-i-center\">\n      <ng-container *ngIf=\"!selected.length\">\n        <em class=\"text-12 text-muted d-flex a-i-center fit-h-20\">{{ 'None' | translate }}</em>\n      </ng-container>\n      <span\n        class=\"tag tag--info chip flex-no-shrink\"\n        *ngFor=\"let selectedItem of selected\"\n      >\n        <button\n          class=\"btn btn-xs btn-clean text-10\"\n          title=\"{{ selectedItem.name }}\"\n          [disabled]=\"disabled\"\n          type=\"button\"\n          (click)=\"config.multi ? deselect(selectedItem) : deselectAll()\"\n        >\n          <i c8yIcon=\"times\"></i>\n        </button>\n        {{ selectedItem.name || (ASSET_CONST | translate: { assetId: selectedItem.id }) }}\n      </span>\n    </div>\n  </div>\n</div>\n\n<div\n  class=\"p-r-16\"\n  *ngIf=\"selectedDevice\"\n>\n  <div class=\"d-flex p-b-4\">\n    <p\n      class=\"text-medium p-t-8 m-r-8\"\n      *ngIf=\"selectedDevice; else multiAssets\"\n    >\n      {{ 'Selected asset' | translate }}\n    </p>\n    <ng-template #multiAssets>\n      <p class=\"text-medium m-r-8\">{{ 'Selected assets' | translate }}</p>\n    </ng-template>\n    <button\n      class=\"btn btn-default btn-xs a-s-center m-t-4 m-l-auto\"\n      title=\"{{ 'Change' | translate }}\"\n      type=\"button\"\n      (click)=\"$event.stopPropagation(); clearSelectedDevices()\"\n    >\n      {{ 'Change' | translate }}\n    </button>\n  </div>\n  <div class=\"d-flex\">\n    <i\n      class=\"text-success p-l-0 p-r-8\"\n      [c8yIcon]=\"'check-circle'\"\n    ></i>\n    <i\n      class=\"m-r-4 icon-20\"\n      [c8yIcon]=\"selectedDevice | getGroupIcon | async\"\n    ></i>\n    <span>{{ selectedDevice.name }}</span>\n  </div>\n</div>\n\n<!-- miller columns header -->\n<div\n  class=\"miller-column__header sticky-top bg-inherit separator-bottom\"\n  *ngIf=\"\n    config.view === 'miller' &&\n    ((config.showFilter && !rootNode.root) || config.columnHeaders) &&\n    !selectedDevice\n  \"\n  [ngClass]=\"{ 'p-t-8': config.search }\"\n>\n  <div class=\"d-flex\" *ngIf=\"config.singleColumn && index !== 0\">\n    <i\n      class=\"m-r-4\"\n      c8yIcon=\"home\"\n    ></i>\n    <p\n      class=\"text-12 text-muted text-truncate text-rtl m-b-4\"\n      title=\"{{ rootNode.breadcrumb || rootNode.label | translate }}\"\n    >\n      {{ rootNode.breadcrumb || rootNode.label | translate }}\n    </p>\n  </div>\n  <div\n    class=\"d-flex a-i-center\"\n    *ngIf=\"config.columnHeaders || config.singleColumn\"\n  >\n    <button\n      class=\"btn btn-default btn-xs m-r-8 p-t-0 p-b-0 p-l-4 p-r-4 l-h-1\"\n      title=\"{{ 'Back' | translate }}\"\n      type=\"button\"\n      *ngIf=\"config.singleColumn && !rootNode.root && index !== 0\"\n      (click)=\"$event.stopPropagation(); back()\"\n    >\n      <i c8yIcon=\"angle-left\"></i>\n    </button>\n    <label\n      class=\"c8y-radio checkbox-inline m-r-8\"\n      *ngIf=\"!rootNode.root && index === 0 && (config.groupsSelectable || !rootNode.isGroup())\"\n    >\n      <input\n        title=\"{{ 'Select group' | translate }}\"\n        type=\"radio\"\n        (change)=\"select(rootNode.mo)\"\n        [checked]=\"isGroupSelected()\"\n      />\n      <span></span>\n    </label>\n\n    <i\n      class=\"icon-20 c8y-icon-duocolor m-r-4\"\n      [c8yIcon]=\"this.rootNode.mo | getGroupIcon: true : this.rootNode.icon | async\"\n      *ngIf=\"config.columnHeaders && (config.singleColumn || (!rootNode.root && index === 0))\"\n    ></i>\n    <p\n      class=\"text-truncate\"\n      title=\"{{ rootNode.label | translate }}\"\n      *ngIf=\"config.columnHeaders\"\n      [ngClass]=\"{\n        'text-label-small': !config.singleColumn,\n        'text-medium': config.singleColumn\n      }\"\n    >\n      {{ rootNode.label | translate }}\n    </p>\n  </div>\n\n  <div\n    *ngIf=\"!rootNode.root && config.showFilter\"\n    [ngClass]=\"{ 'p-t-4': config.columnHeaders }\"\n  >\n    <div class=\"input-group input-group-sm input-group-search\">\n      <input\n        class=\"form-control text-truncate\"\n        placeholder=\"{{ 'Filter this column\u2026' | translate }}\"\n        [(ngModel)]=\"filterText\"\n        (keyup.enter)=\"applyFilter('*' + filterText + '*')\"\n      />\n      <span class=\"input-group-btn\">\n        <button\n          class=\"btn btn-dot p-l-4\"\n          title=\"{{ 'Apply filter' | translate }}\"\n          type=\"button\"\n          (click)=\"applyFilter('*' + filterText + '*')\"\n        >\n          <i c8yIcon=\"filter\"></i>\n        </button>\n        <button\n          class=\"btn btn-dot p-l-4\"\n          title=\" {{ 'Clear filters' | translate }}\"\n          type=\"button\"\n          (click)=\"clearFilters()\"\n          *ngIf=\"filterText.length\"\n        >\n          <i c8yIcon=\"times\"></i>\n        </button>\n      </span>\n    </div>\n  </div>\n</div>\n\n<c8y-asset-selector-node\n  class=\"d-block bg-inherit p-relative\"\n  style=\"z-index: 9\"\n  *ngIf=\"rootNode && !selectedDevice\"\n  [node]=\"rootNode\"\n  [rootNode]=\"rootNode\"\n  [preselected]=\"selected\"\n  [multi]=\"config.multi\"\n  [view]=\"config.view\"\n  [index]=\"index\"\n  [active]=\"active\"\n  [disabled]=\"disabled\"\n  [handleNextMillerViewColumn]=\"handleNextMillerViewColumn.bind(this)\"\n  (isLoadingState)=\"onLoading($event)\"\n  (onSelect)=\"onSelect($event)\"\n  (onDeselect)=\"onDeselect($event)\"\n  [ngClass]=\"{ 'collapse show': !rootNode.root && !rootNode.hidden && config.view !== 'miller' }\"\n></c8y-asset-selector-node>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SearchInputComponent, selector: "c8y-search-input", inputs: ["mode", "enableCustomTemplatePlaceholder", "customPlaceholder", "externalTerm", "customDataQuery", "container", "groupsOnly"], outputs: ["filter", "search", "reset", "onClick", "onOpenToggle"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: AssetSelectorNodeComponent, selector: "c8y-asset-selector-node", inputs: ["node", "rootNode", "preselected", "showPath", "multi", "view", "index", "active", "handleNextMillerViewColumn", "disabled"], outputs: ["isLoadingState", "onSelect", "onDeselect"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: GetGroupIconPipe, name: "getGroupIcon" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-asset-selector', providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            multi: true,
                            useExisting: forwardRef(() => AssetSelectorComponent)
                        },
                        {
                            provide: NG_VALIDATORS,
                            useExisting: forwardRef(() => AssetSelectorComponent),
                            multi: true
                        },
                        AssetSelectorService
                    ], imports: [
                        NgIf,
                        NgStyle,
                        NgClass,
                        SearchInputComponent,
                        NgFor,
                        IconDirective,
                        FormsModule,
                        AssetSelectorNodeComponent,
                        C8yTranslatePipe,
                        AsyncPipe,
                        GetGroupIconPipe
                    ], template: "<div\n  class=\"bg-inherit p-t-8 p-b-8 separator-bottom p-r-16 p-l-16 sticky-top\"\n  [ngStyle]=\"{ 'z-index': config.singleColumn && config.search ? '30' : '20' }\"\n  #header\n  [ngClass]=\"{\n    'p-absolute miller-column__selection d-flex flex-wrap a-i-center': config.view === 'miller',\n    'miller-column__search': config.search,\n    'miller-column__show-selected': config.showSelected\n  }\"\n  *ngIf=\"\n    !selectedDevice &&\n    index === 0 &&\n    (config.search || config.multi || config.label?.length || config.showSelected)\n  \"\n>\n  <p\n    class=\"text-medium flex-no-shrink a-s-center\"\n    [ngClass]=\"{\n      'm-b-4': config.search && root && config.singleColumn,\n      'm-r-8': !config.singleColumn && !config.search,\n      'fit-w': config.singleColumn\n    }\"\n    *ngIf=\"config.label?.length\"\n  >\n    {{ config.label | translate }}\n  </p>\n  <c8y-search-input\n    [ngStyle]=\"{ 'min-width': !config.singleColumn && config.label.length ? '50%' : 'inherit' }\"\n    *ngIf=\"config.search && root\"\n    (onClick)=\"onSearchResultClick($event)\"\n    (reset)=\"onSearchResultReset($event)\"\n    [mode]=\"'select'\"\n    [container]=\"container\"\n    class=\"input-group-sm\"\n    [groupsOnly]=\"config.groupsOnly\"\n    [ngClass]=\"{\n      'm-l-auto': !config.singleColumn && config.label.length,\n      'fit-w': config.singleColumn || !config.label.length\n    }\"\n  ></c8y-search-input>\n  <div\n    class=\"bg-inherit d-flex gap-4 fit-w\"\n    *ngIf=\"config.showSelected\"\n    [ngClass]=\"{ 'm-t-4': config.search }\"\n  >\n    <span class=\"text-label-small m-r-4 a-s-center l-h-1\">{{ 'Selected' | translate }}</span>\n    <div class=\"flex-grow inner-scroll d-flex gap-4 a-i-center\">\n      <ng-container *ngIf=\"!selected.length\">\n        <em class=\"text-12 text-muted d-flex a-i-center fit-h-20\">{{ 'None' | translate }}</em>\n      </ng-container>\n      <span\n        class=\"tag tag--info chip flex-no-shrink\"\n        *ngFor=\"let selectedItem of selected\"\n      >\n        <button\n          class=\"btn btn-xs btn-clean text-10\"\n          title=\"{{ selectedItem.name }}\"\n          [disabled]=\"disabled\"\n          type=\"button\"\n          (click)=\"config.multi ? deselect(selectedItem) : deselectAll()\"\n        >\n          <i c8yIcon=\"times\"></i>\n        </button>\n        {{ selectedItem.name || (ASSET_CONST | translate: { assetId: selectedItem.id }) }}\n      </span>\n    </div>\n  </div>\n</div>\n\n<div\n  class=\"p-r-16\"\n  *ngIf=\"selectedDevice\"\n>\n  <div class=\"d-flex p-b-4\">\n    <p\n      class=\"text-medium p-t-8 m-r-8\"\n      *ngIf=\"selectedDevice; else multiAssets\"\n    >\n      {{ 'Selected asset' | translate }}\n    </p>\n    <ng-template #multiAssets>\n      <p class=\"text-medium m-r-8\">{{ 'Selected assets' | translate }}</p>\n    </ng-template>\n    <button\n      class=\"btn btn-default btn-xs a-s-center m-t-4 m-l-auto\"\n      title=\"{{ 'Change' | translate }}\"\n      type=\"button\"\n      (click)=\"$event.stopPropagation(); clearSelectedDevices()\"\n    >\n      {{ 'Change' | translate }}\n    </button>\n  </div>\n  <div class=\"d-flex\">\n    <i\n      class=\"text-success p-l-0 p-r-8\"\n      [c8yIcon]=\"'check-circle'\"\n    ></i>\n    <i\n      class=\"m-r-4 icon-20\"\n      [c8yIcon]=\"selectedDevice | getGroupIcon | async\"\n    ></i>\n    <span>{{ selectedDevice.name }}</span>\n  </div>\n</div>\n\n<!-- miller columns header -->\n<div\n  class=\"miller-column__header sticky-top bg-inherit separator-bottom\"\n  *ngIf=\"\n    config.view === 'miller' &&\n    ((config.showFilter && !rootNode.root) || config.columnHeaders) &&\n    !selectedDevice\n  \"\n  [ngClass]=\"{ 'p-t-8': config.search }\"\n>\n  <div class=\"d-flex\" *ngIf=\"config.singleColumn && index !== 0\">\n    <i\n      class=\"m-r-4\"\n      c8yIcon=\"home\"\n    ></i>\n    <p\n      class=\"text-12 text-muted text-truncate text-rtl m-b-4\"\n      title=\"{{ rootNode.breadcrumb || rootNode.label | translate }}\"\n    >\n      {{ rootNode.breadcrumb || rootNode.label | translate }}\n    </p>\n  </div>\n  <div\n    class=\"d-flex a-i-center\"\n    *ngIf=\"config.columnHeaders || config.singleColumn\"\n  >\n    <button\n      class=\"btn btn-default btn-xs m-r-8 p-t-0 p-b-0 p-l-4 p-r-4 l-h-1\"\n      title=\"{{ 'Back' | translate }}\"\n      type=\"button\"\n      *ngIf=\"config.singleColumn && !rootNode.root && index !== 0\"\n      (click)=\"$event.stopPropagation(); back()\"\n    >\n      <i c8yIcon=\"angle-left\"></i>\n    </button>\n    <label\n      class=\"c8y-radio checkbox-inline m-r-8\"\n      *ngIf=\"!rootNode.root && index === 0 && (config.groupsSelectable || !rootNode.isGroup())\"\n    >\n      <input\n        title=\"{{ 'Select group' | translate }}\"\n        type=\"radio\"\n        (change)=\"select(rootNode.mo)\"\n        [checked]=\"isGroupSelected()\"\n      />\n      <span></span>\n    </label>\n\n    <i\n      class=\"icon-20 c8y-icon-duocolor m-r-4\"\n      [c8yIcon]=\"this.rootNode.mo | getGroupIcon: true : this.rootNode.icon | async\"\n      *ngIf=\"config.columnHeaders && (config.singleColumn || (!rootNode.root && index === 0))\"\n    ></i>\n    <p\n      class=\"text-truncate\"\n      title=\"{{ rootNode.label | translate }}\"\n      *ngIf=\"config.columnHeaders\"\n      [ngClass]=\"{\n        'text-label-small': !config.singleColumn,\n        'text-medium': config.singleColumn\n      }\"\n    >\n      {{ rootNode.label | translate }}\n    </p>\n  </div>\n\n  <div\n    *ngIf=\"!rootNode.root && config.showFilter\"\n    [ngClass]=\"{ 'p-t-4': config.columnHeaders }\"\n  >\n    <div class=\"input-group input-group-sm input-group-search\">\n      <input\n        class=\"form-control text-truncate\"\n        placeholder=\"{{ 'Filter this column\u2026' | translate }}\"\n        [(ngModel)]=\"filterText\"\n        (keyup.enter)=\"applyFilter('*' + filterText + '*')\"\n      />\n      <span class=\"input-group-btn\">\n        <button\n          class=\"btn btn-dot p-l-4\"\n          title=\"{{ 'Apply filter' | translate }}\"\n          type=\"button\"\n          (click)=\"applyFilter('*' + filterText + '*')\"\n        >\n          <i c8yIcon=\"filter\"></i>\n        </button>\n        <button\n          class=\"btn btn-dot p-l-4\"\n          title=\" {{ 'Clear filters' | translate }}\"\n          type=\"button\"\n          (click)=\"clearFilters()\"\n          *ngIf=\"filterText.length\"\n        >\n          <i c8yIcon=\"times\"></i>\n        </button>\n      </span>\n    </div>\n  </div>\n</div>\n\n<c8y-asset-selector-node\n  class=\"d-block bg-inherit p-relative\"\n  style=\"z-index: 9\"\n  *ngIf=\"rootNode && !selectedDevice\"\n  [node]=\"rootNode\"\n  [rootNode]=\"rootNode\"\n  [preselected]=\"selected\"\n  [multi]=\"config.multi\"\n  [view]=\"config.view\"\n  [index]=\"index\"\n  [active]=\"active\"\n  [disabled]=\"disabled\"\n  [handleNextMillerViewColumn]=\"handleNextMillerViewColumn.bind(this)\"\n  (isLoadingState)=\"onLoading($event)\"\n  (onSelect)=\"onSelect($event)\"\n  (onDeselect)=\"onDeselect($event)\"\n  [ngClass]=\"{ 'collapse show': !rootNode.root && !rootNode.hidden && config.view !== 'miller' }\"\n></c8y-asset-selector-node>\n" }]
        }], ctorParameters: () => [{ type: GroupNodeService }, { type: i2.InventoryService }, { type: AssetSelectorService }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
                type: Input
            }], active: [{
                type: Input
            }], index: [{
                type: Input
            }], asset: [{
                type: Input
            }], selectedDevice: [{
                type: Input
            }], selected: [{
                type: Input
            }], rootNode: [{
                type: Input
            }], selectedItems: [{
                type: Input
            }], container: [{
                type: Input
            }], isNodeSelectable: [{
                type: Input
            }], disabled: [{
                type: Input
            }], onSelected: [{
                type: Output
            }], onClearSelected: [{
                type: Output
            }], onRowSelected: [{
                type: Output
            }], onLoad: [{
                type: Output
            }], header: [{
                type: ViewChild,
                args: ['header']
            }] } });

class MillerViewComponent extends AssetSelectorBase {
    constructor(groupNodeService, inventory, assetSelectorService, cd) {
        super(groupNodeService, inventory, assetSelectorService);
        this.groupNodeService = groupNodeService;
        this.inventory = inventory;
        this.assetSelectorService = assetSelectorService;
        this.cd = cd;
        /**
         * Config object containing all options for the asset selector.
         */
        this.config = {};
        /**
         * The asset to use as root.
         */
        this.asset = undefined;
        /**
         * The selected devices.
         */
        this.selectedDevice = undefined;
        /**
         * Emits if the selection changes
         */
        this.onSelected = new EventEmitter();
        /**
         * Emits if the currently selected asset is cleared.
         */
        this.onClearSelected = new EventEmitter();
        /**
         * The column array will contain all currently selected nodes
         *  which will form the miller view columns (only one per level).
         */
        this.columns = [];
        /**
         * The current filter applied. Used for the empty state text in the view.
         */
        this.filterText = '';
        this.container = 'body';
        this.configOptionsDefault = {
            ...CONFIG_OPTIONS_DEFAULT,
            view: 'miller'
        };
        /**
         * Cached padding top value to avoid recalculating on every change detection cycle.
         */
        this._cachedPaddingTop = null;
    }
    /**
     * @ignore
     */
    async ngOnInit() {
        this.config = { ...this.configOptionsDefault, ...this.config };
        this._calculatePaddingTop();
        const isAnyNodeDefined = !this.rootNode && (!this.asset || Object.keys(this.asset).length === 0);
        if (isAnyNodeDefined) {
            this.rootNode = this.createDefaultRootNode();
        }
        const isOnlyAssetNodeDefined = this.asset && Object.keys(this.asset).length !== 0;
        if (isOnlyAssetNodeDefined) {
            this.rootNode = await this.createManagedObjectRootNode(this.asset);
            this.rootNode.click();
            if (!this.config.preventInitialSelect) {
                const selected = this.selectedDevice || this.asset;
                this.selected = [selected];
                this.emitChange(this.selected, selected, true);
            }
        }
        this.columns.push(this.rootNode);
    }
    /**
     * @ignore
     */
    async ngOnChanges(changes) {
        if (changes.config) {
            this._calculatePaddingTop();
        }
        if (changes.asset && changes.asset.currentValue && !this.asset) {
            this.columns = [];
            this.columns.push(await this.createManagedObjectRootNode(changes.asset.currentValue));
        }
        if (changes.selectedDevice && changes.selectedDevice.currentValue) {
            this.selected = [this.selectedDevice];
            this.emitChange(this.selected, this.selectedDevice, true);
        }
    }
    /**
     * Returns the cached padding top value for the miller view header.
     * The value is calculated once during initialization and when config changes.
     *
     * @returns A unit value (as string) that is multiplied by --c8y-unit-base CSS custom property
     */
    getPaddingTop() {
        return this._cachedPaddingTop || '0';
    }
    /**
     * Create a new column with the selected node as root.
     */
    addNewColumn(node) {
        const level = node.index;
        this.selectedDevice = node.selectedDevices || undefined;
        if (node.root) {
            window.requestAnimationFrame(() => (this.millerViewWrapper.nativeElement.scrollLeft = 0));
            this.columns.length = 1;
        }
        const isLevelLowerThanColumnNumber = level < this.columns.length - 1;
        let goBack = false;
        if (isLevelLowerThanColumnNumber) {
            this.columns.length = level + 1;
            goBack = true;
        }
        if (!node.root && !(this.config.singleColumn && goBack)) {
            this.columns.push(node.nodeCopy);
            // eslint-disable-next-line @typescript-eslint/no-this-alias
            const col = this;
            window.requestAnimationFrame(() => (col.millerViewWrapper.nativeElement.scrollLeft = 50000));
            /* triggered twice with a small delay as a workaround for dealing with Safari's bug:
             "Safari doesn’t update the scrollTop / scrollLeft properties of an element its any of its
              ancestors has overflow set to hidden."
              This was reported on customer portal with the ticket SI-488573
            */
            setTimeout(function () {
                window.requestAnimationFrame(() => (col.millerViewWrapper.nativeElement.scrollLeft = 55000));
            }, 700);
        }
    }
    /**
     * Change the loading state of the asset selector.
     */
    onLoad(event) {
        this.isLoading = event.loading;
        this.filterText = event.filterText;
        this.columnIndex = event.index;
        this.selectedDevice = event.selectedDevice;
        this.cd.detectChanges();
    }
    /**
     * Add the selected node to the selected array.
     */
    onSelectionChange(event) {
        if (!this.config.multi) {
            this.deselectAll(event.change.item);
            return;
        }
        if (event.change.isSelected) {
            this.select(event.change.item);
            return;
        }
        this.deselect(event.change.item);
    }
    /**
     * Calculates and caches the padding top value based on configuration options.
     *
     * The method determines the appropriate header height by considering:
     * - Whether selected items are shown (showSelected)
     * - Whether search functionality is enabled (search)
     * - Whether single column mode is active (singleColumn)
     * - Whether a label is present (label length > 0)
     */
    _calculatePaddingTop() {
        const PADDING = {
            NONE: '0',
            BASE: '5',
            WITH_SEARCH: '6',
            WITH_LABEL: '7',
            WITH_SEARCH_AND_SELECTED: '9',
            WITH_SEARCH_LABEL_SELECTED_SINGLE: '11'
        };
        const { showSelected = false, search = false, singleColumn = false, label } = this.config;
        const hasLabel = label?.length > 0;
        const state = { showSelected, search, singleColumn, hasLabel };
        if (state.showSelected && state.search && state.singleColumn && state.hasLabel) {
            this._cachedPaddingTop = PADDING.WITH_SEARCH_LABEL_SELECTED_SINGLE;
        }
        else if (state.showSelected && state.search) {
            this._cachedPaddingTop = PADDING.WITH_SEARCH_AND_SELECTED;
        }
        else if (state.showSelected && state.hasLabel) {
            this._cachedPaddingTop = PADDING.WITH_LABEL;
        }
        else if (state.showSelected) {
            this._cachedPaddingTop = PADDING.BASE;
        }
        else if (state.search && state.singleColumn && state.hasLabel) {
            this._cachedPaddingTop = PADDING.WITH_SEARCH_AND_SELECTED;
        }
        else if (state.search) {
            this._cachedPaddingTop = PADDING.WITH_SEARCH;
        }
        else if (state.hasLabel) {
            this._cachedPaddingTop = PADDING.BASE;
        }
        else {
            this._cachedPaddingTop = PADDING.NONE;
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MillerViewComponent, deps: [{ token: GroupNodeService }, { token: i2.InventoryService }, { token: AssetSelectorService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: MillerViewComponent, isStandalone: true, selector: "c8y-asset-selector-miller", inputs: { config: "config", asset: "asset", selectedDevice: "selectedDevice", rootNode: "rootNode", container: "container" }, outputs: { onSelected: "onSelected", onClearSelected: "onClearSelected" }, providers: [
            {
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => MillerViewComponent),
                multi: true
            }
        ], viewQueries: [{ propertyName: "millerViewWrapper", first: true, predicate: ["millerViewWrapper"], descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div\n  class=\"miller-view-wrapper {{ config.label }}\"\n  [ngStyle]=\"{\n    'padding-top': 'calc('+ getPaddingTop() +' * var(--c8y-unit-base))',\n  }\"\n  #millerViewWrapper\n  [ngClass]=\"{\n    'single-column': config.singleColumn,\n  }\"\n>\n  <div\n    class=\"miller-column bg-inherit\"\n    *ngFor=\"let column of columns; index as i\"\n  >\n    <c8y-asset-selector\n      class=\"bg-inherit\"\n      #assetSelector\n      [config]=\"config\"\n      [index]=\"i\"\n      [active]=\"columns[i + 1]\"\n      [rootNode]=\"column\"\n      [selected]=\"selected || []\"\n      [selectedDevice]=\"selectedDevice\"\n      (onSelected)=\"onSelectionChange($event)\"\n      (onClearSelected)=\"onClearSelected.emit()\"\n      (onRowSelected)=\"addNewColumn($event)\"\n      (onLoad)=\"onLoad($event)\"\n      [container]=\"container\"\n    ></c8y-asset-selector>\n    <div\n      class=\"p-relative p-b-64\"\n      *ngIf=\"isLoading && columnIndex === i && !selectedDevice\"\n    >\n      <c8y-loading></c8y-loading>\n    </div>\n\n    <div\n      class=\"p-l-8 p-r-8\"\n      *ngIf=\"!column.children.length && !isLoading\"\n    >\n      <c8y-ui-empty-state\n        [icon]=\"'folder-open'\"\n        [title]=\"'No results to display.' | translate\"\n        [subtitle]=\"'The selected asset has no children.' | translate\"\n        *ngIf=\"!filterText; else noSearchResults\"\n        [horizontal]=\"true\"\n      ></c8y-ui-empty-state>\n      <ng-template #noSearchResults>\n        <c8y-ui-empty-state\n          [icon]=\"'folder-open'\"\n          [title]=\"'No results to display for the current filter.' | translate\"\n          [subtitle]=\"'There are no assets matching the filter.' | translate\"\n          [horizontal]=\"true\"\n        ></c8y-ui-empty-state>\n      </ng-template>\n    </div>\n  </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: AssetSelectorComponent, selector: "c8y-asset-selector", inputs: ["config", "active", "index", "asset", "selectedDevice", "selected", "rootNode", "selectedItems", "container", "isNodeSelectable", "disabled"], outputs: ["onSelected", "onClearSelected", "onRowSelected", "onLoad"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MillerViewComponent, decorators: [{
            type: Component,
            args: [{ selector: 'c8y-asset-selector-miller', providers: [
                        {
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: forwardRef(() => MillerViewComponent),
                            multi: true
                        }
                    ], imports: [
                        NgStyle,
                        NgClass,
                        NgFor,
                        AssetSelectorComponent,
                        NgIf,
                        LoadingComponent,
                        EmptyStateComponent,
                        C8yTranslatePipe
                    ], template: "<div\n  class=\"miller-view-wrapper {{ config.label }}\"\n  [ngStyle]=\"{\n    'padding-top': 'calc('+ getPaddingTop() +' * var(--c8y-unit-base))',\n  }\"\n  #millerViewWrapper\n  [ngClass]=\"{\n    'single-column': config.singleColumn,\n  }\"\n>\n  <div\n    class=\"miller-column bg-inherit\"\n    *ngFor=\"let column of columns; index as i\"\n  >\n    <c8y-asset-selector\n      class=\"bg-inherit\"\n      #assetSelector\n      [config]=\"config\"\n      [index]=\"i\"\n      [active]=\"columns[i + 1]\"\n      [rootNode]=\"column\"\n      [selected]=\"selected || []\"\n      [selectedDevice]=\"selectedDevice\"\n      (onSelected)=\"onSelectionChange($event)\"\n      (onClearSelected)=\"onClearSelected.emit()\"\n      (onRowSelected)=\"addNewColumn($event)\"\n      (onLoad)=\"onLoad($event)\"\n      [container]=\"container\"\n    ></c8y-asset-selector>\n    <div\n      class=\"p-relative p-b-64\"\n      *ngIf=\"isLoading && columnIndex === i && !selectedDevice\"\n    >\n      <c8y-loading></c8y-loading>\n    </div>\n\n    <div\n      class=\"p-l-8 p-r-8\"\n      *ngIf=\"!column.children.length && !isLoading\"\n    >\n      <c8y-ui-empty-state\n        [icon]=\"'folder-open'\"\n        [title]=\"'No results to display.' | translate\"\n        [subtitle]=\"'The selected asset has no children.' | translate\"\n        *ngIf=\"!filterText; else noSearchResults\"\n        [horizontal]=\"true\"\n      ></c8y-ui-empty-state>\n      <ng-template #noSearchResults>\n        <c8y-ui-empty-state\n          [icon]=\"'folder-open'\"\n          [title]=\"'No results to display for the current filter.' | translate\"\n          [subtitle]=\"'There are no assets matching the filter.' | translate\"\n          [horizontal]=\"true\"\n        ></c8y-ui-empty-state>\n      </ng-template>\n    </div>\n  </div>\n</div>\n" }]
        }], ctorParameters: () => [{ type: GroupNodeService }, { type: i2.InventoryService }, { type: AssetSelectorService }, { type: i0.ChangeDetectorRef }], propDecorators: { config: [{
                type: Input
            }], asset: [{
                type: Input
            }], selectedDevice: [{
                type: Input
            }], rootNode: [{
                type: Input
            }], onSelected: [{
                type: Output
            }], onClearSelected: [{
                type: Output
            }], millerViewWrapper: [{
                type: ViewChild,
                args: ['millerViewWrapper']
            }], container: [{
                type: Input
            }] } });

class AssetSelectorModule {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorModule, imports: [CoreModule,
            CommonModule,
            CollapseModule,
            BsDropdownModule,
            CoreSearchModule,
            FormsModule,
            AssetSelectorNodeComponent,
            AssetSelectorComponent,
            MillerViewComponent], exports: [AssetSelectorNodeComponent, AssetSelectorComponent, MillerViewComponent] }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorModule, providers: [GroupNodeService, AssetSelectorService], imports: [CoreModule,
            CommonModule,
            CollapseModule,
            BsDropdownModule,
            CoreSearchModule,
            FormsModule,
            AssetSelectorComponent,
            MillerViewComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetSelectorModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [
                        CoreModule,
                        CommonModule,
                        CollapseModule,
                        BsDropdownModule,
                        CoreSearchModule,
                        FormsModule,
                        AssetSelectorNodeComponent,
                        AssetSelectorComponent,
                        MillerViewComponent
                    ],
                    exports: [AssetSelectorNodeComponent, AssetSelectorComponent, MillerViewComponent],
                    providers: [GroupNodeService, AssetSelectorService]
                }]
        }] });

class AssetsNavigatorModule {
    static config(config = {}) {
        return {
            ngModule: AssetsNavigatorModule,
            providers: [
                {
                    provide: ASSET_NAVIGATOR_CONFIG,
                    useValue: config
                }
            ]
        };
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetsNavigatorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: AssetsNavigatorModule, imports: [ModalModule, AssetSelectorModule, DeviceStatusModule] }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetsNavigatorModule, providers: [ModalService, AssetNodeService, hookNavigator(AssetNodeFactory)], imports: [ModalModule, AssetSelectorModule, DeviceStatusModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AssetsNavigatorModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [],
                    imports: [ModalModule, AssetSelectorModule, DeviceStatusModule],
                    providers: [ModalService, AssetNodeService, hookNavigator(AssetNodeFactory)]
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { ASSET_NAVIGATOR_CONFIG, AssetNode, AssetNodeFactory, AssetNodeService, AssetSelectorComponent, AssetSelectorModule, AssetSelectorNodeComponent, AssetsNavigatorAction, AssetsNavigatorModule, CONFIG_OPTIONS_DEFAULT, DeviceGroupService, DynamicGroupNode, GroupNode, GroupNodeService, LoadMoreNode, MillerViewComponent };
//# sourceMappingURL=c8y-ngx-components-assets-navigator.mjs.map