@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
2,067 lines • 116 kB
JavaScript
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