Park Byung Eun 176eb2bbd6 0528 sync
2020-05-28 21:52:25 +09:00

286 lines
7.1 KiB
TypeScript

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
Input,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
EventEmitter,
Output
} from '@angular/core';
import { trigger, transition, style, animate } from '@angular/animations';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlattener, MatTree } from '@angular/material/tree';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { LoginResponse } from '@ucap/protocol-authentication';
import { DeptInfo } from '@ucap/protocol-query';
import { LogService } from '@ucap/ng-logger';
import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
export interface OrganizationNode {
deptInfo: DeptInfo;
ancestorSeq?: number;
children?: OrganizationNode[];
}
export interface FlatNode {
expandable: boolean;
level: number;
data: OrganizationNode;
}
@Component({
selector: 'ucap-organization-tree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.scss'],
animations: [
trigger('removeAdd', [
transition('remove <=> add', [
style({
transform: `rotate(45deg)`,
opacity: 0
}),
animate('.2s 0s ease-out')
])
])
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TreeComponent implements OnInit, OnDestroy {
@Input()
loginRes: LoginResponse;
@Input()
set deptInfoList(data: {
deptInfoList: DeptInfo[];
expanded?: number[];
displayRoot?: boolean;
}) {
const deptInfoList = data.deptInfoList;
const displayRoot =
undefined === data.displayRoot ? true : data.displayRoot;
if (!deptInfoList || 0 === deptInfoList.length) {
return;
}
const nodeMap = new Map<number, OrganizationNode>();
let rootNodeList: OrganizationNode[] = [];
const remainChildNodeList: OrganizationNode[] = [];
deptInfoList.forEach((deptInfo) => {
const node: OrganizationNode = {
deptInfo,
children: []
};
if (nodeMap.has(deptInfo.seq)) {
this.logService.warn('duplicate seq', deptInfo.seq);
return;
}
nodeMap.set(deptInfo.seq, node);
if (0 === deptInfo.parentSeq) {
rootNodeList.push(node);
return;
}
if (nodeMap.has(deptInfo.parentSeq)) {
const ancestor = nodeMap.get(deptInfo.parentSeq);
node.ancestorSeq = ancestor.deptInfo.seq;
ancestor.children.push(node);
} else {
remainChildNodeList.push(node);
}
});
if (
!displayRoot &&
0 < rootNodeList.length &&
0 === rootNodeList[0].deptInfo.parentSeq &&
!!rootNodeList[0].children &&
0 < rootNodeList[0].children.length
) {
rootNodeList = [...rootNodeList[0].children];
}
remainChildNodeList.forEach((node) => {
const ancestor = nodeMap.get(node.deptInfo.parentSeq);
if (!!ancestor) {
node.ancestorSeq = ancestor.deptInfo.seq;
ancestor.children.push(node);
}
});
this.dataSource.data = rootNodeList;
if (!!data.expanded) {
this.expand(...data.expanded);
}
this.changeDetectorRef.detectChanges();
}
@Output() currentDeptSeqChange: EventEmitter<number> = new EventEmitter();
@Input() set currentDeptSeq(deptInfoSeq: number) {
this._currentDeptSeq = deptInfoSeq;
}
get currentDeptSeq() {
return this._currentDeptSeq;
}
// tslint:disable-next-line: variable-name
_currentDeptSeq: number;
@Output()
clickNode = new EventEmitter<DeptInfo>();
@ViewChild('treeList', { static: false })
treeList: MatTree<FlatNode>;
@ViewChild('cvsvList', { static: false })
cvsvList: CdkVirtualScrollViewport;
@ViewChild(PerfectScrollbarDirective, { static: false })
psDirectiveRef?: PerfectScrollbarDirective;
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;
// tslint:disable-next-line: variable-name
private _ngOnDestroySubject: Subject<void>;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {
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,
level,
data: node
};
},
(node) => node.level,
(node) => node.expandable,
(node) => node.children
);
this.dataSource = new VirtualScrollTreeFlatDataSource<
OrganizationNode,
FlatNode
>(this.treeControl, this.treeFlattener);
}
ngOnInit(): void {
this._ngOnDestroySubject = new Subject();
this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
this.treeControl.expansionModel.changed
.pipe(takeUntil(this._ngOnDestroySubject))
.subscribe(() => {
this.cvsvList.checkViewportSize();
this.psDirectiveRef.update();
});
}
ngOnDestroy(): void {
if (!!this._ngOnDestroySubject) {
this._ngOnDestroySubject.next();
this._ngOnDestroySubject.complete();
}
}
hasChild = (_: number, node: FlatNode) => node.expandable;
trackBy = (_: number, node: FlatNode) => node.data.deptInfo.seq;
onClickNode(event: Event, node: FlatNode) {
event.stopPropagation();
this.clickNode.emit(node.data.deptInfo);
}
expand(...deptSeq: number[]) {
if (!this.treeControl.dataNodes || !deptSeq || 0 === deptSeq.length) {
return;
}
const flatNodes: FlatNode[] = [];
deptSeq.forEach((s) => {
const node = this.treeControl.dataNodes.find(
(n) => n.data.deptInfo.seq === s
);
if (!!node) {
flatNodes.push(node);
this.selectHierarchy(flatNodes, node);
}
});
if (0 < flatNodes.length) {
this.treeControl.expansionModel.select(...flatNodes);
}
}
expandAll() {
this.treeControl.expandAll();
}
collapse(deptSeq: number) {
if (!this.treeControl.dataNodes) {
return;
}
const node = this.treeControl.dataNodes.find(
(n) => n.data.deptInfo.seq === deptSeq
);
if (!!node) {
this.treeControl.collapse(node);
}
}
collapseAll() {
this.treeControl.collapseAll();
}
private selectHierarchy(flatNodes: FlatNode[], flatNode: FlatNode): void {
// tslint:disable-next-line: variable-name
let _flatNode = flatNode;
while (true) {
if (!_flatNode) {
return;
}
const ancestorSeq = _flatNode.data.ancestorSeq;
if (!ancestorSeq) {
return;
}
_flatNode = this.treeControl.dataNodes.find(
(n) => n.data.deptInfo.seq === ancestorSeq
);
if (!_flatNode) {
return;
}
const i = flatNodes.findIndex(
(n) => n.data.deptInfo.seq === _flatNode.data.deptInfo.seq
);
if (-1 === i) {
flatNodes.push(_flatNode);
}
}
}
}