| /******************************************************************************** |
| * 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 ''; |
| } |
| } |
| } |