blob: 7b56a983e2d6e17cb700de3a3bdd3aa3b7b1714c [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
import {Component, OnInit} from '@angular/core';
import {TreeNode, MenuItem} from 'primeng/primeng';
import {map, tap, startWith, filter, flatMap} from 'rxjs/operators';
import {MDMItem} from '../core/mdm-item';
import {Node} from '../navigator/node';
import {NodeService} from '../navigator/node.service';
import {QueryService} from '../tableview/query.service';
import {NodeproviderService} from './nodeprovider.service';
import {NavigatorService} from './navigator.service';
import {BasketService} from '../basket/basket.service';
import {MDMNotificationService} from '../core/mdm-notification.service';
import { TranslateService } from '@ngx-translate/core';
import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
import { ActivatedRoute } from '@angular/router';
import { forkJoin, combineLatest } from 'rxjs';
@Component({
selector: 'mdm-navigator',
templateUrl: './mdm-navigator.component.html',
styles: [
'>>>.ui-tree { overflow: auto; max-height: calc(100vh - 7em); min-height: calc(100vh - 7em); '
+ 'padding: .25em .25em .5em .25em !important; }',
'>>>.ui-tree .ui-tree-container { overflow: visible; }'
]
})
export class MDMNavigatorComponent implements OnInit {
AlrtItemTypeNotSupported = 'This node type is not supported by the current view!';
selectedNodes: TreeNode[] = [];
nodes: TreeNode[] = [];
lastClickTime = 0;
loadingNode = <TreeNode>{
label: 'Loading subordinate items...',
leaf: true,
icon: 'fa fa-spinner fa-pulse fa-fw'
};
contextMenuItems: MenuItem[] = [
{ label: 'Add to shopping basket', icon: 'fa fa-shopping-cart', command: (event) => this.addSelectionToBasket() }
];
constructor(private nodeService: NodeService,
private queryService: QueryService,
private basketService: BasketService,
private nodeproviderService: NodeproviderService,
private navigatorService: NavigatorService,
private notificationService: MDMNotificationService,
private translateService: TranslateService,
private route: ActivatedRoute) {
}
ngOnInit() {
streamTranslate(this.translateService, TRANSLATE('navigator.mdm-navigator.loading-subordinate-items')).subscribe(
(msg: string) => this.loadingNode.label = msg);
streamTranslate(this.translateService, TRANSLATE('navigator.mdm-navigator.add-to-shopping-basket')).subscribe(
(msg: string) => this.contextMenuItems[0].label = msg);
streamTranslate(this.translateService, TRANSLATE('navigator.mdm-navigator.msg-item-type-not-supported')).subscribe(
(msg: string) => this.AlrtItemTypeNotSupported = msg);
// make sure navigate parameter is handled after nodes are loaded
this.loadRootNodes().subscribe(
treeNodes => {
this.nodes = treeNodes;
this.loadNavigateItems().subscribe(selectItems => {
this.navigatorService.fireOnOpenInTree(selectItems);
});
},
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
);
this.nodeproviderService.nodeProviderChanged.subscribe(
np => this.reloadTree(),
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
);
this.navigatorService.onOpenInTree
.subscribe(
items => this.openInTree(items),
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node-in-navigation-tree'), error)
);
}
loadRootNodes() {
return this.nodeService.getRootNodes()
.pipe(
map(n => n.map(node => this.mapNode(node)))
);
}
loadNavigateItems() {
return this.route.queryParamMap
.pipe(
map(params => params.get('navigate')),
filter(url => url !== null),
flatMap(url => this.nodeService.getNodesByUrl(url)),
map(nodes => nodes.map(node => new MDMItem(node.sourceName, node.type, node.id)))
);
}
nodeSelect(event) {
if (event.node.data) {
this.navigatorService.setSelectedItem(event.node.data);
}
if (event.originalEvent.timeStamp - this.lastClickTime < 300) {
if (!event.node.expanded && !event.node.children) {
this.loadNode(event);
}
event.node.expanded = !event.node.expanded;
}
this.lastClickTime = event.originalEvent.timeStamp;
}
loadNode(event) {
if (event.node && event.node.children == undefined && !event.node.leaf) {
return this.getChildren(event.node).pipe(
startWith([this.loadingNode]),
tap(nodes => (nodes && nodes.length === 0) ? event.node.leaf = true : event.node.leaf = false))
.subscribe(
nodes => event.node.children = nodes,
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
);
}
}
reloadTree() {
this.loadRootNodes()
.subscribe(
n => this.nodes = n,
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
);
}
onFocus(event: { eventName: string, node: TreeNode }) {
this.navigatorService.setSelectedItem(event.node.data.item);
}
getNodeClass(item: MDMItem) {
return 'icon ' + item.type.toLowerCase();
}
mapNode(node: Node) {
let item = new MDMItem(node.sourceName, node.type, node.id);
return <TreeNode>{
label: node.name,
leaf: this.nodeproviderService.getSubNodeprovider(item) == undefined,
data: item,
icon: this.getNodeClass(item)
};
}
getChildren(node: TreeNode) {
return this.nodeService.getNodesByUrl(this.nodeproviderService.getQueryForChildren(node.data)).pipe(
map(nodes => nodes.map(n => this.mapNode(n))),
map(treenodes => treenodes.sort((n1, n2) => n1.label.localeCompare(n2.label))));
}
addSelectionToBasket() {
this.basketService.addAll(this.selectedNodes.map(node => <MDMItem>node.data));
}
openInTree(items: MDMItem[]) {
this.selectedNodes = [];
items.forEach(item => {
let pathTypes = this.nodeproviderService.getPathTypes(item.type);
if (pathTypes.length === 0) {
alert(this.AlrtItemTypeNotSupported);
} else {
let env = this.nodes.find(e => item.source === e.data.source);
env.expanded = true;
this.openChildrenRecursive(item, env, pathTypes, 1);
}
});
}
openChildrenRecursive(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
if (current.children) {
this.expander(item, current, pathTypes, iii);
} else {
this.getChildren(current).pipe(
startWith([this.loadingNode]),
tap(n => current.children = n))
.subscribe(
() => this.expander(item, current, pathTypes, iii),
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
);
}
}
expander(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
let expandList: string[] = [];
this.nodeService.searchNodes('filter=' + item.type + '.Id eq "' + item.id + '"',
item.source, pathTypes[iii])
.subscribe(
nodes => {
expandList = nodes.map(n => n.id);
current.children.filter(node => expandList.findIndex(
i => node.data ? i === node.data.id : false) > -1
)
.forEach(node => {
if (++iii < pathTypes.length) {
node.expanded = true;
this.openChildrenRecursive(item, node, pathTypes, iii);
this.scrollToSelectionPrimeNgDataTable(node);
} else {
this.selectedNodes.push(node);
let length = this.selectedNodes.length;
if (length === 1) {
this.nodeService.getNodeFromItem(this.selectedNodes[length - 1].data)
.subscribe(
n => this.navigatorService.setSelectedNode(n),
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
);
}
this.scrollToSelectionPrimeNgDataTable(node);
}
});
},
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
);
}
/**
* PrimeNG does not support scroll to view. This methods implements a
* workaround by using HTML element IDs.
* @see https://github.com/primefaces/primeng/issues/1278
*/
scrollToSelectionPrimeNgDataTable(node: TreeNode) {
let list = document.querySelectorAll('p-tree span#' + this.getId(node));
if (list && list.length > 0) {
list.item(0).scrollIntoView();
}
}
getId(node: TreeNode) {
if (node && node.data) {
return 'node_' + node.data.source + '_' + node.data.type + '_' + node.data.id;
} else {
return '';
}
}
}