virtual scroll of organization is implemented

This commit is contained in:
병준 박 2019-11-15 10:13:18 +09:00
parent 241fd65d31
commit 50c8c2a02d
11 changed files with 355 additions and 215 deletions

View File

@ -33,10 +33,10 @@
.mat-tab-labels {
flex-flow: column;
height: 280px;
padding-top:10px;
padding-top: 10px;
.mat-tab-label {
height: 80px;
padding:0 16px;
padding: 0 16px;
}
}
.mat-ink-bar {
@ -63,7 +63,7 @@
justify-content: center;
align-items: center;
transform: scale(0.9);
transition: transform .3s cubic-bezier(.4,0,0,1);
transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1);
svg {
width: 24px;
@ -82,7 +82,7 @@
.mat-tab-label-content {
.icon-item {
background: #ef4c73;
transform: scale(1.0);
transform: scale(1);
/*svg {
stroke: #ef4c73;
fill: #ef4c73;

View File

@ -71,7 +71,7 @@ export class GroupComponent implements OnInit, OnDestroy {
UserInfo | UserInfoSS | UserInfoF | UserInfoDN
>();
@ViewChild('groupExpansionPanel', { static: true })
@ViewChild('groupExpansionPanel', { static: false })
groupExpansionPanel: GroupExpansionPanelComponent;
@ViewChild('profileContextMenuTrigger', { static: true })

View File

@ -10,7 +10,7 @@
>
</ucap-organization-tenant-search>
</div>
<div class="oraganization-tab" *ngIf="departmentInfoList$ | async">
<div class="oraganization-tab">
<div class="oraganization-tab-tree">
<ucap-organization-tree
[oraganizationList]="departmentInfoList$ | async"

View File

@ -6,7 +6,7 @@ import {
ElementRef,
AfterViewInit,
Output,
EventEmitter,
EventEmitter
} from '@angular/core';
import {
ucapAnimations,
@ -20,7 +20,7 @@ import {
AlertDialogData,
AlertDialogResult,
FileUploadQueueComponent,
StringUtil,
StringUtil
} from '@ucap-webmessenger/ui';
import { Store, select } from '@ngrx/store';
import { NGXLogger } from 'ngx-logger';
@ -33,7 +33,7 @@ import {
isRecallable,
InfoResponse,
EventJson,
FileEventJson,
FileEventJson
} from '@ucap-webmessenger/protocol-event';
import * as AppStore from '@app/store';
@ -47,36 +47,36 @@ import {
EnvironmentsInfo,
KEY_ENVIRONMENTS_INFO,
UserSelectDialogType,
RightDrawer,
RightDrawer
} from '@app/types';
import { RoomInfo, UserInfo, RoomType } from '@ucap-webmessenger/protocol-room';
import { tap, take, map, catchError } from 'rxjs/operators';
import {
FileInfo,
FormComponent as UCapUiChatFormComponent,
FormComponent as UCapUiChatFormComponent
} from '@ucap-webmessenger/ui-chat';
import { KEY_VER_INFO } from '@app/types/ver-info.type';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import {
MatMenuTrigger,
MatSnackBarRef,
SimpleSnackBar,
SimpleSnackBar
} from '@angular/material';
import {
CommonApiService,
FileUploadItem,
FileTalkSaveRequest,
FileTalkSaveResponse,
FileTalkSaveResponse
} from '@ucap-webmessenger/api-common';
import {
CreateChatDialogComponent,
CreateChatDialogData,
CreateChatDialogResult,
CreateChatDialogResult
} from '../dialogs/chat/create-chat.dialog.component';
import {
FileViewerDialogComponent,
FileViewerDialogData,
FileViewerDialogResult,
FileViewerDialogResult
} from '@app/layouts/common/dialogs/file-viewer.dialog.component';
import { CONST, FileUtil } from '@ucap-webmessenger/core';
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
@ -84,12 +84,12 @@ import { StatusCode } from '@ucap-webmessenger/api';
import {
EditChatRoomDialogComponent,
EditChatRoomDialogResult,
EditChatRoomDialogData,
EditChatRoomDialogData
} from '../dialogs/chat/edit-chat-room.dialog.component';
import {
SelectGroupDialogComponent,
SelectGroupDialogResult,
SelectGroupDialogData,
SelectGroupDialogData
} from '../dialogs/group/select-group.dialog.component';
import { GroupDetailData } from '@ucap-webmessenger/protocol-sync';
@ -97,7 +97,7 @@ import { GroupDetailData } from '@ucap-webmessenger/protocol-sync';
selector: 'app-layout-messenger-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.scss'],
animations: ucapAnimations,
animations: ucapAnimations
})
export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
@Output()
@ -379,7 +379,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
// duration: 3000,
verticalPosition: 'bottom',
horizontalPosition: 'center',
panelClass: ['chat-snackbar-class'],
panelClass: ['chat-snackbar-class']
});
this.snackBarPreviewEvent.onAction().subscribe(() => {
this.setEventMoreInit();
@ -420,7 +420,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
EventStore.info({
roomSeq: this.roomInfo.roomSeq,
baseSeq: seq,
requestCount: CONST.EVENT_INFO_READ_COUNT,
requestCount: CONST.EVENT_INFO_READ_COUNT
})
);
}
@ -438,8 +438,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
width: '360px',
data: {
title: 'Alert',
message: `대화내용을 입력해주세요.`,
},
message: `대화내용을 입력해주세요.`
}
});
return;
}
@ -453,8 +453,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
roomSeq: this.roomInfo.roomSeq,
eventType: EventType.MassText,
// sentMessage: message.replace(/\n/gi, '\r\n')
sentMessage: message,
},
sentMessage: message
}
})
);
} else {
@ -464,8 +464,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
req: {
roomSeq: this.roomInfo.roomSeq,
eventType: EventType.Character,
sentMessage: message,
},
sentMessage: message
}
})
);
}
@ -479,7 +479,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
onMassDetail(value: number) {
this.store.dispatch(
ChatStore.selectedMassDetail({
massEventSeq: value,
massEventSeq: value
})
);
}
@ -491,7 +491,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
FileViewerDialogResult
>(FileViewerDialogComponent, {
position: {
top: '30px',
top: '30px'
},
maxWidth: '100vw',
maxHeight: '100vh',
@ -504,8 +504,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
downloadUrl: this.sessionVerInfo.downloadUrl,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
userSeq: this.loginRes.userSeq,
},
userSeq: this.loginRes.userSeq
}
});
}
@ -531,7 +531,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
const info = {
senderSeq: this.loginRes.userSeq,
roomSeq: this.roomInfo.roomSeq,
roomSeq: this.roomInfo.roomSeq
};
const allObservables: Observable<FileTalkSaveResponse>[] = [];
@ -552,7 +552,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
'rv',
'ts',
'webm',
'wmv',
'wmv'
].indexOf(FileUtil.getExtension(fileUploadItem.file.name))
) {
thumbnail = await FileUtil.thumbnail(fileUploadItem.file);
@ -567,7 +567,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
file: fileUploadItem.file,
fileName: fileUploadItem.file.name,
thumb: thumbnail,
fileUploadItem,
fileUploadItem
};
allObservables.push(
@ -599,8 +599,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
req: {
roomSeq: info.roomSeq,
eventType: EventType.File,
sentMessage: res.returnJson,
},
sentMessage: res.returnJson
}
})
);
}
@ -626,7 +626,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
this.messageContextMenuTrigger.menu.focusFirstItem('mouse');
this.messageContextMenuTrigger.menuData = {
message: params.message,
loginRes: this.loginRes,
loginRes: this.loginRes
};
this.messageContextMenuTrigger.openMenu();
}
@ -646,7 +646,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
this.snackBarService.open('클립보드에 복사되었습니다.', '', {
duration: 3000,
verticalPosition: 'top',
horizontalPosition: 'center',
horizontalPosition: 'center'
});
}
}
@ -658,7 +658,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
userSeq: this.loginRes.userSeq,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
eventMassSeq: message.seq,
eventMassSeq: message.seq
})
.pipe(take(1))
.subscribe(res => {
@ -669,7 +669,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
{
duration: 3000,
verticalPosition: 'top',
horizontalPosition: 'center',
horizontalPosition: 'center'
}
);
}
@ -693,8 +693,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
data: {
type: UserSelectDialogType.MessageForward,
title: 'MessageForward',
ignoreRoom: [this.roomInfo],
},
ignoreRoom: [this.roomInfo]
}
});
if (!!result && !!result.choice && result.choice) {
@ -718,10 +718,10 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
req: {
roomSeq: '-999',
eventType: message.type,
sentMessage: message.sentMessage,
sentMessage: message.sentMessage
},
trgtUserSeqs: userSeqs,
trgtRoomSeq: roomSeq,
trgtRoomSeq: roomSeq
})
);
}
@ -737,9 +737,9 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
req: {
roomSeq: '-999',
eventType: message.type,
sentMessage: message.sentMessage,
sentMessage: message.sentMessage
},
trgtUserSeqs: [this.loginRes.talkWithMeBotSeq],
trgtUserSeqs: [this.loginRes.talkWithMeBotSeq]
})
);
}
@ -755,15 +755,15 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
width: '220px',
data: {
title: 'Delete',
html: `선택한 메시지를 삭제하시겠습니까?<br/>삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.`,
},
html: `선택한 메시지를 삭제하시겠습니까?<br/>삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.`
}
});
if (!!result && !!result.choice && result.choice) {
this.store.dispatch(
EventStore.del({
roomSeq: this.roomInfo.roomSeq,
eventSeq: message.seq,
eventSeq: message.seq
})
);
}
@ -779,8 +779,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
width: '220px',
data: {
title: 'ReCall',
html: `해당 대화를 회수하시겠습니까?<br/>상대방 대화창에서도 회수됩니다.`,
},
html: `해당 대화를 회수하시겠습니까?<br/>상대방 대화창에서도 회수됩니다.`
}
});
if (!!result && !!result.choice && result.choice) {
@ -788,7 +788,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
EventStore.cancel({
roomSeq: this.roomInfo.roomSeq,
eventSeq: message.seq,
deviceType: this.environmentsInfo.deviceType,
deviceType: this.environmentsInfo.deviceType
})
);
}
@ -805,7 +805,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
{
this.store.dispatch(
ChatStore.selectedRightDrawer({
req: RightDrawer.AlbumBox,
req: RightDrawer.AlbumBox
})
);
}
@ -814,7 +814,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
{
this.store.dispatch(
ChatStore.selectedRightDrawer({
req: RightDrawer.FileBox,
req: RightDrawer.FileBox
})
);
}
@ -823,7 +823,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
{
this.store.dispatch(
ChatStore.selectedRightDrawer({
req: RightDrawer.RoomUser,
req: RightDrawer.RoomUser
})
);
}
@ -841,8 +841,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
title: 'Edit Chat Member',
curRoomUser: this.userInfoList.filter(
user => user.seq !== this.loginRes.userSeq
),
},
)
}
});
if (!!result && !!result.choice && result.choice) {
@ -864,8 +864,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
RoomStore.inviteOrOpen({
req: {
divCd: 'Invite',
userSeqs,
},
userSeqs
}
})
);
}
@ -881,8 +881,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
>(SelectGroupDialogComponent, {
width: '600px',
data: {
title: 'Group Select',
},
title: 'Group Select'
}
});
if (!!result && !!result.choice && result.choice) {
@ -899,7 +899,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
this.store.dispatch(
SyncStore.updateGroupMember({
oldGroup,
trgtUserSeq,
trgtUserSeq
})
);
}
@ -916,8 +916,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
width: '600px',
data: {
title: 'Edit Chat Room',
roomInfo: this.roomInfo,
},
roomInfo: this.roomInfo
}
});
if (!!result && !!result.choice && result.choice) {
@ -934,8 +934,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
roomName,
receiveAlarm: roomInfo.receiveAlarm,
syncAll:
roomNameChangeTarget.toUpperCase() === 'ALL' ? true : false,
},
roomNameChangeTarget.toUpperCase() === 'ALL' ? true : false
}
})
);
@ -947,7 +947,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
this.store.dispatch(
RoomStore.updateTimeRoomInterval({
roomSeq: roomInfo.roomSeq,
timerInterval: timeRoomInterval,
timerInterval: timeRoomInterval
})
);
}

View File

@ -1,4 +1,4 @@
<div class="chat-messages">
<div class="chat-messages" #scrollMe>
<!-- <div class="message-row" *ngIf="eventRemain">
<button mat-button (click)="onClickMore($event)">이전 대화 보기</button>
</div> -->

View File

@ -1,17 +1,27 @@
<mat-tree
<cdk-virtual-scroll-viewport #cvsvOrganization itemSize="48" fxFlexFill>
<ng-container
*cdkVirtualFor="let node of dataSource.expandedData$"
></ng-container>
<mat-tree
#orgranizationTree
[dataSource]="dataSource"
[treeControl]="treeControl"
class="organization-tree"
>
<mat-nested-tree-node *matTreeNodeDef="let node">
>
<!-- This is the tree node template for leaf nodes -->
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<li>
<div class="mat-tree-node" (click)="onClickNode(node)">
{{ node.title }}
{{ node.name }}
</div>
</li>
</mat-nested-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasChildren" class="tree-node-frame">
</mat-tree-node>
<!-- This is the tree node template for expandable nodes -->
<mat-tree-node
*matTreeNodeDef="let node; when: hasChild"
matTreeNodePadding
class="tree-node-frame"
>
<li>
<div class="mat-tree-node" (click)="onClickNode(node)" class="path">
<span class="horizontal-line"></span>
@ -26,10 +36,12 @@
{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}
</mat-icon>
</button>
<span class="dept-name">{{ node.title }}</span>
<span class="dept-name">{{ node.name }}</span>
</div>
<ul
[class.organization-tree-node-invisible]="!treeControl.isExpanded(node)"
[class.organization-tree-node-invisible]="
!treeControl.isExpanded(node)
"
>
<div *ngIf="treeControl.isExpanded(node)" class="boxnone">
<div class="vertical-line"></div>
@ -37,5 +49,6 @@
</div>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</mat-tree-node>
</mat-tree>
</cdk-virtual-scroll-viewport>

View File

@ -1,6 +1,6 @@
@charset 'utf-8';
.organization-tree {
padding:10px;
padding: 10px;
ul,
li {
margin-top: 0;
@ -13,83 +13,83 @@
}
}
.tree-node-frame{
li{
.path{
.horizontal-line{
display:none;
.tree-node-frame {
li {
.path {
.horizontal-line {
display: none;
}
}
}
.mat-tree-node {
min-height: 30px;
font-size: 13px;
padding-left:20px;
margin-top:4px;
padding-left: 20px;
margin-top: 4px;
&:hover {
background-color: #f4f4f4;
border:1px solid #cccccc;
border-radius:4px;
border: 1px solid #cccccc;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(32, 33, 36, 0.1);
}
}
}
ul .tree-node-frame li .path > .horizontal-line{
display:inline-block;
ul .tree-node-frame li .path > .horizontal-line {
display: inline-block;
}
.boxnone{
position:relative;
.vertical-line{
background: rgba(189,189,189,.4);
.boxnone {
position: relative;
.vertical-line {
background: rgba(189, 189, 189, 0.4);
bottom: 6px;
display: block;
position: absolute;
top: 0px;
width: 2px;
}
.mat-nested-tree-node:last-child{
padding-bottom:10px;
.mat-tree-node:last-child {
padding-bottom: 10px;
}
}
.path {
padding: 6px 4px;
+ ul{
li:last-chlid{
border-bottom:1px solid #dddddd;
+ ul {
li:last-chlid {
border-bottom: 1px solid #dddddd;
}
}
.horizontal-line{
width:10px;
height:1px;
.horizontal-line {
width: 10px;
height: 1px;
background-color: #dddddd;
display:inline-block;
display: inline-block;
vertical-align: middle;
margin-left:-10px;
margin-left: -10px;
}
.mat-icon-button{
.mat-icon-button {
padding: 0;
min-width: 0;
width: 20px;
height: 20px;
flex-shrink: 0;
line-height: 20px;
.mat-icon-rtl-mirror{
.mat-icon-rtl-mirror {
border: 1px solid #dddddd;
padding: 2px;
font-size: 14px;
min-width: 14px;
min-height: 14px;
line-height: 14px;
width:20px;
height:20px;
width: 20px;
height: 20px;
box-shadow: 0 2px 1px rgba(48, 48, 48, 0.2);
border-radius: 50%;
}
}
.dept-name{
padding-left:10px;
.dept-name {
padding-left: 10px;
}
}

View File

@ -8,39 +8,26 @@ import {
EventEmitter,
AfterViewInit
} from '@angular/core';
import { MatTreeNestedDataSource, MatTree } from '@angular/material';
import { NestedTreeControl } from '@angular/cdk/tree';
import { BehaviorSubject } from 'rxjs';
import { MatTreeFlattener, MatTree } from '@angular/material';
import { NGXLogger } from 'ngx-logger';
import { DeptInfo } 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';
export class OraganizationNode {
private childNodeBehaviorSubject: BehaviorSubject<OraganizationNode[]>;
private childNodeList: OraganizationNode[];
interface OrganizationNode {
deptInfo: DeptInfo;
name: string;
children?: OrganizationNode[];
}
get title(): string {
return this.deptInfo.name;
}
get children(): BehaviorSubject<OraganizationNode[]> {
if (!this.childNodeBehaviorSubject) {
this.childNodeBehaviorSubject = new BehaviorSubject(
undefined === this.childNodeList ? [] : this.childNodeList
);
}
return this.childNodeBehaviorSubject;
}
constructor(public deptInfo: DeptInfo) {}
addChild(childNode: OraganizationNode) {
if (!this.childNodeList) {
this.childNodeList = [];
}
this.childNodeList.push(childNode);
}
/** Flat node with expandable and level information */
interface FlatNode {
expandable: boolean;
name: string;
level: number;
}
@Component({
@ -55,87 +42,96 @@ export class TreeComponent implements OnInit, AfterViewInit {
@Input()
loginRes: LoginResponse;
@Input()
set oraganizationList(deptInfo: DeptInfo[]) {
const nodeMap = new Map<number, OraganizationNode>();
const rootNodeList: OraganizationNode[] = [];
const remainChildNodeList: OraganizationNode[] = [];
let myNode: OraganizationNode;
deptInfo.forEach(value => {
const node = new OraganizationNode(value);
if (nodeMap.has(value.seq)) {
this.logger.warn('duplicate seq', value.seq);
set oraganizationList(deptInfoList: DeptInfo[]) {
if (!deptInfoList || 0 === deptInfoList.length) {
return;
}
nodeMap.set(value.seq, node);
const nodeMap = new Map<number, OrganizationNode>();
const rootNodeList: OrganizationNode[] = [];
const remainChildNodeList: OrganizationNode[] = [];
let myNode: OrganizationNode;
if (value.seq === this.loginRes.departmentCode) {
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 === value.parentSeq) {
if (0 === deptInfo.parentSeq) {
rootNodeList.push(node);
return;
}
if (nodeMap.has(value.parentSeq)) {
nodeMap.get(value.parentSeq).addChild(node);
if (nodeMap.has(deptInfo.parentSeq)) {
nodeMap.get(deptInfo.parentSeq).children.push(node);
} else {
remainChildNodeList.push(node);
}
});
remainChildNodeList.forEach(value => {
if (nodeMap.has(value.deptInfo.parentSeq)) {
nodeMap.get(value.deptInfo.parentSeq).addChild(value);
remainChildNodeList.forEach(node => {
if (nodeMap.has(node.deptInfo.parentSeq)) {
nodeMap.get(node.deptInfo.parentSeq).children.push(node);
}
});
this.dataSource.data = rootNodeList;
// console.log('myNode', myNode);
// console.log('this.dataSource.data', this.dataSource.data[0]);
// this.treeControl.expandDescendants(this.dataSource.data[2]);
// console.log('this.dataSource', this.dataSource);
// console.log('this.dataSource.data', this.dataSource.data);
// console.log('this.treeControl', this.treeControl);
// console.log('this.treeControl.dataNodes', this.treeControl.dataNodes);
// const myNode = this.treeControl.dataNodes.filter(
// node => node.deptInfo.seq === this.loginRes.departmentCode
// );
// if (!!myNode && myNode.length > 0) {
// this.treeControl.expand(myNode[0]);
// }
}
@ViewChild('orgranizationTree', { static: true })
orgranizationTree: MatTree<OraganizationNode>;
@ViewChild('cvsvOrganization', { static: false })
cvsvOrganization: CdkVirtualScrollViewport;
levels = new Map<OraganizationNode, number>();
treeControl: NestedTreeControl<OraganizationNode>;
@ViewChild('orgranizationTree', { static: false })
orgranizationTree: MatTree<OrganizationNode>;
dataSource: MatTreeNestedDataSource<OraganizationNode>;
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private logger: NGXLogger
) {
this.treeControl = new NestedTreeControl<OraganizationNode>(
this.getChildren
this.treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable
);
this.dataSource = new MatTreeNestedDataSource();
this.treeFlattener = new MatTreeFlattener<OrganizationNode, FlatNode>(
(node: OrganizationNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level
};
},
node => node.level,
node => node.expandable,
node => node.children
);
this.dataSource = new VirtualScrollTreeFlatDataSource<
OrganizationNode,
FlatNode
>(this.treeControl, this.treeFlattener);
}
ngOnInit() {}
ngAfterViewInit(): void {}
ngAfterViewInit(): void {
this.dataSource.cdkVirtualScrollViewport = this.cvsvOrganization;
}
getChildren = (node: OraganizationNode) => node.children;
hasChild = (_: number, node: FlatNode) => node.expandable;
hasChildren = (index: number, node: OraganizationNode) =>
0 < node.children.value.length;
onClickNode(node: OraganizationNode) {
onClickNode(node: OrganizationNode) {
this.selected.emit(node.deptInfo);
}
}

View File

@ -2,6 +2,8 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { MatButtonModule } from '@angular/material/button';
@ -21,6 +23,7 @@ const SERVICES = [];
CommonModule,
ReactiveFormsModule,
FlexLayoutModule,
ScrollingModule,
MatButtonModule,

View File

@ -0,0 +1,126 @@
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import {
BehaviorSubject,
merge,
Observable,
Subject,
Subscription
} from 'rxjs';
import { map, share } from 'rxjs/operators';
import { MatTreeFlattener } from '@angular/material';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
private flattenedDataSubject = new BehaviorSubject<F[]>([]);
private expandedDataSubject = new BehaviorSubject<F[]>([]);
expandedData$: Observable<F[]>;
private connectSubject: Subject<F[]>;
private dataChangeSubscription: Subscription;
private rangeChangeSubscription: Subscription;
private rangeSubject: BehaviorSubject<{
start: number;
end: number;
}>;
// tslint:disable-next-line: variable-name
private _cdkVirtualScrollViewport: CdkVirtualScrollViewport;
set cdkVirtualScrollViewport(
cdkVirtualScrollViewport: CdkVirtualScrollViewport
) {
if (!cdkVirtualScrollViewport) {
return;
}
this._cdkVirtualScrollViewport = cdkVirtualScrollViewport;
this.rangeSubject = new BehaviorSubject<{ start: number; end: number }>({
start: 0,
end: 1
});
this.rangeChangeSubscription = cdkVirtualScrollViewport.renderedRangeStream.subscribe(
range => {
this.rangeSubject.next({
start: range.start,
end: range.end
});
if (!!this.connectSubject) {
this.connectSubject.next(
this.expandedDataSubject.value.slice(
this.rangeSubject.value.start,
this.rangeSubject.value.end
)
);
}
}
);
}
// tslint:disable-next-line: variable-name
private _data: BehaviorSubject<T[]>;
get data() {
return this._data.value;
}
set data(value: T[]) {
this._data.next(value);
this.flattenedDataSubject.next(this.treeFlattener.flattenNodes(this.data));
this.treeControl.dataNodes = this.flattenedDataSubject.value;
}
constructor(
private treeControl: FlatTreeControl<F>,
private treeFlattener: MatTreeFlattener<T, F>,
initialData: T[] = []
) {
super();
this._data = new BehaviorSubject<T[]>(initialData);
this.expandedData$ = this.expandedDataSubject.asObservable().pipe(share());
}
connect(collectionViewer: CollectionViewer): Observable<F[]> {
this.connectSubject = new Subject<F[]>();
this.dataChangeSubscription = merge(
collectionViewer.viewChange,
this.treeControl.expansionModel.changed,
this.flattenedDataSubject
)
.pipe(
map(() => {
this.expandedDataSubject.next(
this.treeFlattener.expandFlattenedNodes(
this.flattenedDataSubject.value,
this.treeControl
)
);
return !this.rangeSubject
? this.expandedDataSubject.value
: this.expandedDataSubject.value.slice(
this.rangeSubject.value.start,
this.rangeSubject.value.end
);
})
)
.subscribe(datas => {
this.connectSubject.next(datas);
if (!!this._cdkVirtualScrollViewport) {
this._cdkVirtualScrollViewport.checkViewportSize();
}
});
return this.connectSubject.asObservable();
}
disconnect() {
if (!!this.dataChangeSubscription) {
this.dataChangeSubscription.unsubscribe();
}
if (!!this.rangeChangeSubscription) {
this.rangeChangeSubscription.unsubscribe();
}
}
}

View File

@ -13,6 +13,8 @@ export * from './lib/components/file-upload-queue.component';
export * from './lib/components/file-viewer.component';
export * from './lib/components/float-action-button.component';
export * from './lib/data-source/virtual-scroll-tree-flat.data-source';
export * from './lib/dialogs/alert.dialog.component';
export * from './lib/dialogs/confirm.dialog.component';