349 lines
8.3 KiB
TypeScript
349 lines
8.3 KiB
TypeScript
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<FlatNode>;
|
|
|
|
@ViewChild('cvsvList', { static: false })
|
|
cvsvList: CdkVirtualScrollViewport;
|
|
|
|
@ViewChild(PerfectScrollbarDirective, { static: false })
|
|
psDirectiveRef?: PerfectScrollbarDirective;
|
|
|
|
@ContentChild(ExpansionNodeDirective, {
|
|
read: TemplateRef,
|
|
static: false
|
|
})
|
|
nodeTemplate: TemplateRef<ExpansionNodeDirective>;
|
|
|
|
@ContentChild(ExpansionFavoriteHeaderDirective, {
|
|
read: TemplateRef,
|
|
static: false
|
|
})
|
|
favoriteHeaderTemplate: TemplateRef<ExpansionFavoriteHeaderDirective>;
|
|
|
|
@ContentChild(ExpansionDefaultHeaderDirective, {
|
|
read: TemplateRef,
|
|
static: false
|
|
})
|
|
defaultHeaderTemplate: TemplateRef<ExpansionDefaultHeaderDirective>;
|
|
|
|
@ContentChild(ExpansionBuddyHeaderDirective, {
|
|
read: TemplateRef,
|
|
static: false
|
|
})
|
|
buddyHeaderTemplate: TemplateRef<ExpansionBuddyHeaderDirective>;
|
|
|
|
treeControl: FlatTreeControl<FlatNode>;
|
|
treeFlattener: MatTreeFlattener<GroupNode, FlatNode>;
|
|
dataSource: VirtualScrollTreeFlatDataSource<GroupNode, FlatNode>;
|
|
|
|
NodeType = NodeType;
|
|
|
|
private nodeMap: Map<NodeType, GroupNode[]> = new Map();
|
|
// tslint:disable-next-line: variable-name
|
|
private _ngOnDestroySubject: Subject<void>;
|
|
|
|
constructor(private changeDetectorRef: ChangeDetectorRef) {
|
|
this.treeControl = new FlatTreeControl<FlatNode>(
|
|
(node) => node.level,
|
|
(node) => node.expandable
|
|
);
|
|
|
|
this.treeFlattener = new MatTreeFlattener<GroupNode, FlatNode>(
|
|
(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<GroupNode, 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();
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|