diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.scss b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.scss index c159542f..8d947cf0 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.scss +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.scss @@ -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; diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/group.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/group.component.ts index 68d5eb52..7fa4c7b4 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/group.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/group.component.ts @@ -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 }) diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/organization.component.html b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/organization.component.html index a6cbf034..f1e572f8 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/organization.component.html +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-sidenav/organization.component.html @@ -10,7 +10,7 @@ > -
+
{ 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[] = []; @@ -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: `선택한 메시지를 삭제하시겠습니까?
삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.`, - }, + html: `선택한 메시지를 삭제하시겠습니까?
삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.` + } }); 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: `해당 대화를 회수하시겠습니까?
상대방 대화창에서도 회수됩니다.`, - }, + html: `해당 대화를 회수하시겠습니까?
상대방 대화창에서도 회수됩니다.` + } }); 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 }) ); } diff --git a/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.html b/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.html index efd65825..fc0ad4fa 100644 --- a/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.html +++ b/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.html b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.html index 60f7be47..e7a5e0f6 100644 --- a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.html +++ b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.html @@ -1,41 +1,54 @@ - - -
  • -
    - {{ node.title }} -
    -
  • -
    - -
  • -
    - - - {{ node.title }} -
    -
      -
      -
      - + + + + + +
    • +
      + {{ node.name }}
      -
    -
  • -
    -
    + + + + +
  • +
    + + + {{ node.name }} +
    +
      +
      +
      + +
      +
    +
  • +
    + + diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss index afb8d863..1f74f884 100644 --- a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss +++ b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.scss @@ -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; - &:hover { - background-color: #f4f4f4; - border:1px solid #cccccc; - border-radius:4px; - box-shadow: 0 1px 4px rgba(32, 33, 36, 0.1); + padding-left: 20px; + margin-top: 4px; + &:hover { + background-color: #f4f4f4; + 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; } } diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts index 3af7dc58..37d54111 100644 --- a/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts +++ b/projects/ucap-webmessenger-ui-organization/src/lib/components/tree.component.ts @@ -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; - private childNodeList: OraganizationNode[]; +interface OrganizationNode { + deptInfo: DeptInfo; + name: string; + children?: OrganizationNode[]; +} - get title(): string { - return this.deptInfo.name; - } - - get children(): BehaviorSubject { - 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(); - const rootNodeList: OraganizationNode[] = []; - const remainChildNodeList: OraganizationNode[] = []; - let myNode: OraganizationNode; + set oraganizationList(deptInfoList: DeptInfo[]) { + if (!deptInfoList || 0 === deptInfoList.length) { + return; + } + const nodeMap = new Map(); + const rootNodeList: OrganizationNode[] = []; + const remainChildNodeList: OrganizationNode[] = []; + let myNode: OrganizationNode; - deptInfo.forEach(value => { - const node = new OraganizationNode(value); - if (nodeMap.has(value.seq)) { - this.logger.warn('duplicate seq', value.seq); + 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(value.seq, node); + nodeMap.set(deptInfo.seq, node); - if (value.seq === this.loginRes.departmentCode) { + 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; + @ViewChild('cvsvOrganization', { static: false }) + cvsvOrganization: CdkVirtualScrollViewport; - levels = new Map(); - treeControl: NestedTreeControl; + @ViewChild('orgranizationTree', { static: false }) + orgranizationTree: MatTree; - dataSource: MatTreeNestedDataSource; + treeControl: FlatTreeControl; + treeFlattener: MatTreeFlattener; + dataSource: VirtualScrollTreeFlatDataSource; constructor( private changeDetectorRef: ChangeDetectorRef, private logger: NGXLogger ) { - this.treeControl = new NestedTreeControl( - this.getChildren + this.treeControl = new FlatTreeControl( + node => node.level, + node => node.expandable ); - this.dataSource = new MatTreeNestedDataSource(); + this.treeFlattener = new MatTreeFlattener( + (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); } } diff --git a/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts b/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts index 14ee7887..e4cb4074 100644 --- a/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts +++ b/projects/ucap-webmessenger-ui-organization/src/lib/ucap-ui-organization.module.ts @@ -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, diff --git a/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts b/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts new file mode 100644 index 00000000..c2f66074 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/data-source/virtual-scroll-tree-flat.data-source.ts @@ -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 extends DataSource { + private flattenedDataSubject = new BehaviorSubject([]); + + private expandedDataSubject = new BehaviorSubject([]); + expandedData$: Observable; + + private connectSubject: Subject; + 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; + 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, + private treeFlattener: MatTreeFlattener, + initialData: T[] = [] + ) { + super(); + this._data = new BehaviorSubject(initialData); + this.expandedData$ = this.expandedDataSubject.asObservable().pipe(share()); + } + + connect(collectionViewer: CollectionViewer): Observable { + this.connectSubject = new Subject(); + + 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(); + } + } +} diff --git a/projects/ucap-webmessenger-ui/src/public-api.ts b/projects/ucap-webmessenger-ui/src/public-api.ts index 50a0fdd2..39f04b1a 100644 --- a/projects/ucap-webmessenger-ui/src/public-api.ts +++ b/projects/ucap-webmessenger-ui/src/public-api.ts @@ -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';