ucap-angular/projects/ui-group/src/lib/components/expansion.component.ts
richard-loafle 946eed63ed ing
2020-05-08 15:12:26 +09:00

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();
}
}