import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChild, ContentChild, TemplateRef, ChangeDetectionStrategy, ChangeDetectorRef, Directive } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { FlatTreeControl } from '@angular/cdk/tree'; import { MatTreeFlattener, MatTree } from '@angular/material/tree'; import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui'; import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar'; export enum NodeType { None = 'None', Profile = 'Profile', Favorite = 'Favorite', Buddy = 'Buddy', Default = 'Default' } export interface GroupNode { nodeType: NodeType; userInfo?: UserInfo; groupDetail?: GroupDetailData; children?: GroupNode[]; } export interface FlatNode { expandable: boolean; level: number; node: GroupNode; } @Directive({ selector: '[ucapGroupExpansionNode]' }) export class ExpansionNodeDirective {} @Directive({ selector: '[ucapGroupExpansionFavoriteHeader]' }) export class ExpansionFavoriteHeaderDirective {} @Directive({ selector: '[ucapGroupExpansionDefaultHeader]' }) export class ExpansionDefaultHeaderDirective {} @Directive({ selector: '[ucapGroupExpansionBuddyHeader]' }) export class ExpansionBuddyHeaderDirective {} @Component({ selector: 'ucap-group-expansion', templateUrl: './expansion.component.html', styleUrls: ['./expansion.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class ExpansionComponent implements OnInit, OnDestroy { @Input() displayOrder: NodeType[] = []; @Input() set profile(userInfo: UserInfo) { if (!userInfo) { this.nodeMap.set(NodeType.Profile, []); } else { const node: GroupNode = { nodeType: NodeType.Profile, userInfo, children: [] }; this.nodeMap.set(NodeType.Profile, [node]); } this.refreshNodes(); } @Input() set favorites(userInfos: UserInfo[]) { if (!userInfos || 0 === userInfos.length) { this.nodeMap.set(NodeType.Favorite, []); } else { const node: GroupNode = { nodeType: NodeType.Favorite, groupDetail: { seq: -9999, name: NodeType.Favorite, isActive: true, userSeqs: userInfos.map((userInfo) => String(userInfo.seq)) } as GroupDetailData, children: [] }; userInfos.forEach((userInfo) => { node.children.push({ nodeType: NodeType.Favorite, userInfo }); }); this.nodeMap.set(NodeType.Favorite, [node]); } this.refreshNodes(); } @Input() set groupBuddies(list: { group: GroupDetailData; buddyList: UserInfo[] }[]) { if (!list || 0 === list.length) { this.nodeMap.set(NodeType.Default, []); this.nodeMap.set(NodeType.Buddy, []); } else { this.nodeMap.set(NodeType.Default, []); this.nodeMap.set(NodeType.Buddy, []); for (const item of list) { let nodeType = NodeType.Buddy; if (0 === item.group.seq) { nodeType = NodeType.Default; } const node: GroupNode = { nodeType, groupDetail: item.group, children: [] }; item.buddyList.sort((a, b) => a.order < b.order ? -1 : a.order > b.order ? 1 : a.name < b.name ? -1 : a.name > b.name ? 1 : 0 ); item.buddyList.forEach((userInfo) => { node.children.push({ nodeType, groupDetail: item.group, userInfo }); }); if (NodeType.Buddy === nodeType) { this.nodeMap.get(NodeType.Buddy).push(node); } else { this.nodeMap.get(NodeType.Default).push(node); } } } this.refreshNodes(); } @Input() checkable = false; @Input() selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = []; @Input() unselectableUserList?: ( | UserInfo | UserInfoSS | UserInfoF | UserInfoDN )[] = []; @ViewChild('treeList', { static: false }) treeList: MatTree; @ViewChild('cvsvList', { static: false }) cvsvList: CdkVirtualScrollViewport; @ViewChild(PerfectScrollbarDirective, { static: false }) psDirectiveRef?: PerfectScrollbarDirective; @ContentChild(ExpansionNodeDirective, { read: TemplateRef, static: false }) nodeTemplate: TemplateRef; @ContentChild(ExpansionFavoriteHeaderDirective, { read: TemplateRef, static: false }) favoriteHeaderTemplate: TemplateRef; @ContentChild(ExpansionDefaultHeaderDirective, { read: TemplateRef, static: false }) defaultHeaderTemplate: TemplateRef; @ContentChild(ExpansionBuddyHeaderDirective, { read: TemplateRef, static: false }) buddyHeaderTemplate: TemplateRef; treeControl: FlatTreeControl; treeFlattener: MatTreeFlattener; dataSource: VirtualScrollTreeFlatDataSource; NodeType = NodeType; private nodeMap: Map = new Map(); // tslint:disable-next-line: variable-name private _ngOnDestroySubject: Subject; constructor(private changeDetectorRef: ChangeDetectorRef) { this.treeControl = new FlatTreeControl( (node) => node.level, (node) => node.expandable ); this.treeFlattener = new MatTreeFlattener( (node: GroupNode, level: number) => { return { expandable: !!node.children && node.children.length > 0, level, nodeType: node.nodeType, node }; }, (node) => node.level, (node) => node.expandable, (node) => node.children ); this.dataSource = new VirtualScrollTreeFlatDataSource( 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(); } } onClickHeaderMenu(event: MouseEvent, node: FlatNode) {} isCheckedGroup(node: FlatNode): boolean { const groupDetail = node.node.groupDetail; if (!groupDetail || groupDetail === undefined) { return false; } if (groupDetail.userSeqs.length === 0) { return false; } if (!!this.selectedUserList && this.selectedUserList.length > 0) { let allExist = true; groupDetail.userSeqs.some((seq) => { if ( this.selectedUserList.filter((item) => item.seq === seq).length === 0 ) { allExist = false; return true; } }); return allExist; } return false; } isCheckableGroup(node: FlatNode): boolean { if (!!this.unselectableUserList && this.unselectableUserList.length > 0) { const groupDetail = node.node.groupDetail; let allExist = true; groupDetail.userSeqs.some((seq) => { if ( this.unselectableUserList.filter((item) => item.seq === seq) .length === 0 ) { allExist = false; return true; } }); if (allExist) { return false; } } return true; } onChangeCheckGroup(value: boolean, node: FlatNode) {} isHeader = (_: number, node: FlatNode) => NodeType.Profile !== node.node.nodeType && 0 === node.level; private refreshNodes() { const rootNode: GroupNode[] = []; for (const nodeType of this.displayOrder) { const subNode = this.nodeMap.get(nodeType); if (!!subNode && 0 < subNode.length) { rootNode.push(...subNode); } } this.dataSource.data = rootNode; this.changeDetectorRef.detectChanges(); } }