301 lines
7.7 KiB
TypeScript
Raw Normal View History

import {
Component,
OnInit,
Input,
ChangeDetectorRef,
2019-10-07 10:08:14 +09:00
ViewChild,
Output,
2019-10-31 10:13:30 +09:00
EventEmitter,
2019-11-21 10:29:19 +09:00
AfterViewInit,
OnDestroy
} from '@angular/core';
import { MatTreeFlattener, MatTree } from '@angular/material';
import { NGXLogger } from 'ngx-logger';
import { DeptInfo, DeptType } from '@ucap-webmessenger/protocol-query';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { FlatTreeControl } from '@angular/cdk/tree';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { VirtualScrollTreeFlatDataSource } from '@ucap-webmessenger/ui';
2019-11-25 09:34:12 +09:00
import { ucapAnimations } from '@ucap-webmessenger/ui';
import { trigger, transition, style, animate } from '@angular/animations';
2019-11-26 10:43:37 +09:00
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
2020-02-07 14:42:11 +09:00
import { Subscription, Observable } from 'rxjs';
interface OrganizationNode {
deptInfo: DeptInfo;
name: string;
children?: OrganizationNode[];
}
/** Flat node with expandable and level information */
interface FlatNode {
expandable: boolean;
name: string;
level: number;
deptInfo: DeptInfo;
}
2019-10-04 13:45:02 +09:00
@Component({
selector: 'ucap-organization-tree',
templateUrl: './tree.component.html',
2019-11-25 09:34:12 +09:00
styleUrls: ['./tree.component.scss'],
animations: [
ucapAnimations,
trigger('romvoeAdd', [
transition('remove <=> add', [
style({
transform: `rotate(45deg)`,
opacity: 0
}),
animate('.2s 0s ease-out')
])
])
]
2019-10-04 13:45:02 +09:00
})
2019-11-21 10:29:19 +09:00
export class TreeComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
loginRes: LoginResponse;
2019-11-21 10:29:19 +09:00
2019-10-04 13:45:02 +09:00
@Input()
set oraganizationList(deptInfoList: DeptInfo[]) {
if (!deptInfoList || 0 === deptInfoList.length) {
return;
}
const nodeMap = new Map<number, OrganizationNode>();
const rootNodeList: OrganizationNode[] = [];
const remainChildNodeList: OrganizationNode[] = [];
let myNode: OrganizationNode;
deptInfoList.forEach(deptInfo => {
const node: OrganizationNode = {
deptInfo,
name: deptInfo.name,
children: []
};
if (nodeMap.has(deptInfo.seq)) {
this.logger.warn('duplicate seq', deptInfo.seq);
return;
}
nodeMap.set(deptInfo.seq, node);
if (deptInfo.seq === this.loginRes.departmentCode) {
myNode = node;
}
if (0 === deptInfo.parentSeq) {
rootNodeList.push(node);
return;
}
2019-10-04 13:45:02 +09:00
if (nodeMap.has(deptInfo.parentSeq)) {
nodeMap.get(deptInfo.parentSeq).children.push(node);
} else {
remainChildNodeList.push(node);
}
});
remainChildNodeList.forEach(node => {
if (nodeMap.has(node.deptInfo.parentSeq)) {
nodeMap.get(node.deptInfo.parentSeq).children.push(node);
}
});
this.dataSource.data = rootNodeList;
// 내부서 트리 오픈
if (!this.myDeptRoot) {
this.myDeptRoot = this.getMyRoot(
deptInfoList,
this.loginRes.departmentCode,
[]
);
}
if (!!this.myDeptRoot) {
this.myDeptRoot.reverse().forEach(seq => {
this.treeControl.dataNodes.some(dn => {
if (dn.deptInfo.seq === seq) {
this.treeControl.expand(dn);
return;
}
});
});
}
}
2020-02-07 14:42:11 +09:00
@Input()
activate$: Observable<boolean>;
2020-02-04 11:27:48 +09:00
@Output()
selected = new EventEmitter<DeptInfo>();
@ViewChild('cvsvOrganization', { static: false })
cvsvOrganization: CdkVirtualScrollViewport;
@ViewChild('orgranizationTree', { static: false })
orgranizationTree: MatTree<OrganizationNode>;
2019-11-26 10:43:37 +09:00
@ViewChild(PerfectScrollbarDirective, { static: false })
psDirectiveRef?: PerfectScrollbarDirective;
myDeptRoot: number[];
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;
2019-11-26 10:43:37 +09:00
treeControlExpansionChangeSubscription: Subscription;
2020-02-07 14:42:11 +09:00
activateSubscription: Subscription;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private logger: NGXLogger
) {
this.treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable
);
this.treeFlattener = new MatTreeFlattener<OrganizationNode, FlatNode>(
(node: OrganizationNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level,
deptInfo: node.deptInfo
};
},
node => node.level,
node => node.expandable,
node => node.children
);
this.dataSource = new VirtualScrollTreeFlatDataSource<
OrganizationNode,
FlatNode
>(this.treeControl, this.treeFlattener);
}
2019-10-04 13:45:02 +09:00
2019-11-26 10:43:37 +09:00
ngOnInit() {
this.treeControlExpansionChangeSubscription = this.treeControl.expansionModel.changed.subscribe(
() => {
2020-02-07 17:14:42 +09:00
this.cvsvOrganization.checkViewportSize();
2019-11-26 10:43:37 +09:00
this.psDirectiveRef.update();
}
);
2020-02-07 14:42:11 +09:00
this.activateSubscription = this.activate$.subscribe(activate => {
if (activate) {
setTimeout(() => {
if (!!this.cvsvOrganization) {
this.cvsvOrganization.checkViewportSize();
2020-02-07 17:14:42 +09:00
this.psDirectiveRef.update();
2020-02-07 14:42:11 +09:00
}
}, 100);
}
});
2019-11-26 10:43:37 +09:00
}
2019-11-21 10:29:19 +09:00
ngOnDestroy(): void {
2019-11-26 10:43:37 +09:00
if (!!this.treeControlExpansionChangeSubscription) {
this.treeControlExpansionChangeSubscription.unsubscribe();
}
2020-02-07 14:42:11 +09:00
if (!!this.activateSubscription) {
this.activateSubscription.unsubscribe();
}
2019-11-21 10:29:19 +09:00
}
ngAfterViewInit(): void {
this.dataSource.cdkVirtualScrollViewport = this.cvsvOrganization;
this.cvsvOrganization.scrollToOffset(100);
this.psDirectiveRef.update();
}
hasChild = (_: number, node: FlatNode) => node.expandable;
2019-10-07 10:08:14 +09:00
2019-11-26 10:43:37 +09:00
isLastNode(node: FlatNode, depth: number): boolean {
const i = this.findNodeIndex(node, depth);
if (-1 === i) {
return false;
}
2019-11-25 09:34:12 +09:00
if (i === this.dataSource.expandedDataSubject.value.length - 1) {
return true;
}
2019-11-26 10:43:37 +09:00
const n = this.dataSource.expandedDataSubject.value[i];
for (
let idx = i + 1;
idx < this.dataSource.expandedDataSubject.value.length;
idx++
) {
const element = this.dataSource.expandedDataSubject.value[idx];
if (n.level === element.level) {
return false;
}
if (n.level > element.level) {
return true;
}
2019-11-25 09:34:12 +09:00
}
2019-11-26 10:43:37 +09:00
return true;
2019-11-25 09:34:12 +09:00
}
2019-11-26 10:43:37 +09:00
appendDivArray(level: number): number[] {
if (0 === level) {
return [];
2019-11-25 10:28:59 +09:00
}
2019-11-26 10:43:37 +09:00
return Array.from({ length: level }, (_, i: number) => i);
2019-11-25 10:28:59 +09:00
}
onClickNode(node: OrganizationNode) {
2019-10-07 10:08:14 +09:00
this.selected.emit(node.deptInfo);
}
2019-11-25 10:28:59 +09:00
2019-11-26 10:43:37 +09:00
private findNodeIndex(node: FlatNode, depth: number): number {
if (!node) {
return -1;
}
const i = this.dataSource.expandedDataSubject.value.findIndex(n => {
2019-11-25 10:28:59 +09:00
return node.deptInfo.seq === n.deptInfo.seq;
});
2019-11-26 10:43:37 +09:00
if (0 === depth) {
return i;
2019-11-25 10:28:59 +09:00
}
2019-11-26 10:43:37 +09:00
return this.findNodeIndex(this.findParentNode(i), depth - 1);
}
private findParentNode(index: number): FlatNode {
const node = this.dataSource.expandedDataSubject.value[index];
2019-11-25 10:28:59 +09:00
2019-11-26 10:43:37 +09:00
for (let idx = index - 1; idx >= 0; idx--) {
const n = this.dataSource.expandedDataSubject.value[idx];
if (n.level === node.level - 1) {
return n;
2019-11-25 10:28:59 +09:00
}
}
2019-11-26 10:43:37 +09:00
return undefined;
2019-11-25 10:28:59 +09:00
}
private getMyRoot(
deptInfoList: DeptInfo[],
srcDeptSeq: number,
data: number[]
): number[] {
const arr = deptInfoList.filter(info => info.seq === srcDeptSeq);
if (!!arr && arr.length > 0) {
const info = arr[0];
data.push(info.seq);
if (info.type === DeptType.Root) {
return data;
} else {
return this.getMyRoot(deptInfoList, info.parentSeq, data);
}
} else {
return data;
}
}
2019-10-04 13:45:02 +09:00
}