From f41c5ae5f08dfd3b3cd119760e3f24c47251f0c0 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Mon, 20 Jan 2020 16:40:17 +0900 Subject: [PATCH 1/2] virtual scroll of chat room is implemented --- .../components/messages.component.ts | 26 ++-- .../lib/components/message-box.component.ts | 16 +-- .../lib/components/messages.component.html | 20 +-- .../lib/components/messages.component.scss | 4 + .../src/lib/components/messages.component.ts | 136 ++++++++++++------ 5 files changed, 130 insertions(+), 72 deletions(-) diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts index f5b78937..3672db1a 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/messages.component.ts @@ -7,7 +7,8 @@ import { Output, EventEmitter, Inject, - ChangeDetectorRef + ChangeDetectorRef, + ChangeDetectionStrategy } from '@angular/core'; import { ucapAnimations, @@ -133,7 +134,8 @@ import { FileProtocolService } from '@ucap-webmessenger/protocol-file'; selector: 'app-layout-messenger-messages', templateUrl: './messages.component.html', styleUrls: ['./messages.component.scss'], - animations: ucapAnimations + animations: ucapAnimations, + changeDetection: ChangeDetectionStrategy.OnPush }) export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { @Output() @@ -391,14 +393,14 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { } }); - this.interval = setInterval(() => { - if ( - !!this.roomInfoSubject.value && - !!this.roomInfoSubject.value.isTimeRoom - ) { + if ( + !!this.roomInfoSubject.value && + !!this.roomInfoSubject.value.isTimeRoom + ) { + this.interval = setInterval(() => { this.store.dispatch(EventStore.infoIntervalClear({})); - } - }, 1000); + }, 1000); + } } ngOnDestroy(): void { @@ -424,7 +426,9 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { this.searchEventListProcessingSubscription.unsubscribe(); } - clearInterval(this.interval); + if (!!this.interval) { + clearInterval(this.interval); + } } ngAfterViewInit(): void { @@ -1847,7 +1851,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { this.eventRemainedSubject.value ) { this.moreSearchProcessing = true; - this.chatMessages.storeScrollHeight(); + this.chatMessages.storeScrollPosition(); // Case :: retrieve event infos step by step until include searchtext in event.. this.store.dispatch( diff --git a/projects/ucap-webmessenger-ui-chat/src/lib/components/message-box.component.ts b/projects/ucap-webmessenger-ui-chat/src/lib/components/message-box.component.ts index 29e0694a..789b7d32 100644 --- a/projects/ucap-webmessenger-ui-chat/src/lib/components/message-box.component.ts +++ b/projects/ucap-webmessenger-ui-chat/src/lib/components/message-box.component.ts @@ -111,14 +111,14 @@ export class MessageBoxComponent implements OnInit, AfterViewInit { } ngAfterViewInit(): void { - this.logger.debug( - 'offsetHeight' + this.message.seq, - this.mbContainer.nativeElement.offsetHeight - ); - this.elementRef.nativeElement.style.height = `${this.mbContainer - .nativeElement.offsetHeight + 20}px`; - this.elementRef.nativeElement.style.maxHeight = `${this.mbContainer - .nativeElement.offsetHeight + 20}px`; + // this.logger.debug( + // 'offsetHeight' + this.message.seq, + // this.mbContainer.nativeElement.offsetHeight + // ); + // this.elementRef.nativeElement.style.height = `${this.mbContainer + // .nativeElement.offsetHeight + 20}px`; + // this.elementRef.nativeElement.style.maxHeight = `${this.mbContainer + // .nativeElement.offsetHeight + 20}px`; this.mbContainer.nativeElement.classList.remove('hide'); } 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 7df4f3e0..57ea8b25 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 @@ -9,8 +9,14 @@ (psYReachStart)="onYReachStart($event)" (psYReachEnd)="onYReachEnd($event)" [enableUnequalChildrenSizes]="true" + [modifyOverflowStyleOfParentScroll]="false" + (vsChange)="onVsChange($event)" > -
+
>(); + @ViewChild('chatMessagesContainer', { static: false }) + chatMessagesContainer: ElementRef; + @ViewChild(PerfectScrollbarDirective, { static: false }) psChatContent: PerfectScrollbarDirective; @ViewChild(VirtualScrollerComponent, { static: false }) private virtualScroller: VirtualScrollerComponent; - storedScrollHeight = 0; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩. + storedScrollItem: Info; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩. scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다. firstCheckReadHere = true; initRoomLastEventSeq: number; @@ -175,6 +179,8 @@ export class MessagesComponent implements OnInit, OnDestroy { this.firstCheckReadHere = false; } + this.eventList = eventList; + if (this.searchingMode) { const baseseq = this.baseEventSeq; // setTimeout(() => { @@ -187,8 +193,6 @@ export class MessagesComponent implements OnInit, OnDestroy { this.ready(); } } - - this.eventList = eventList; }); this.newEventListSubscription = this.newEventList$.subscribe( newEventList => { @@ -310,7 +314,9 @@ export class MessagesComponent implements OnInit, OnDestroy { } /** Date Splitter show check */ - getDateSplitter(curIndex: number): boolean { + getDateSplitter(message: Info): boolean { + const curIndex = this.eventList.findIndex(v => v.seq === message.seq); + if (curIndex === 0) { return true; } @@ -318,21 +324,15 @@ export class MessagesComponent implements OnInit, OnDestroy { if (!this.eventList[curIndex]) { return false; } - return ( - this.datePipe.transform( - moment(this.eventList[curIndex].sendDate).toDate(), - 'yyyyMMdd' - ) !== - this.datePipe.transform( - moment(this.eventList[curIndex - 1].sendDate).toDate(), - 'yyyyMMdd' - ) + return !moment(this.eventList[curIndex].sendDate).isSame( + moment(this.eventList[curIndex - 1].sendDate), + 'day' ); } return false; } - getReadHere(messageIndex: number): boolean { + getReadHere(message: Info): boolean { if ( !!this.roomInfo && !!this.roomInfo.lastReadEventSeq && @@ -344,6 +344,10 @@ export class MessagesComponent implements OnInit, OnDestroy { this.roomInfo.roomType === RoomType.Multi ) { if (!this.roomInfo.isTimeRoom) { + const messageIndex = this.eventList.findIndex( + v => v.seq === message.seq + ); + if ( this.eventList[messageIndex].seq === this.roomInfo.lastReadEventSeq + 1 @@ -365,40 +369,51 @@ export class MessagesComponent implements OnInit, OnDestroy { return rtnStr; } - storeScrollHeight() { - this.storedScrollHeight = this.psChatContent.elementRef.nativeElement.scrollHeight; + storeScrollPosition() { + this.storedScrollItem = this.eventList[ + this.virtualScroller.viewPortInfo.startIndex + ]; + } + + scrollToStoredItem() { + if (!this.scrollUpInitalized) { + this.chatMessagesContainer.nativeElement.classList.remove('hide'); + } + + this.virtualScroller.scrollInto(this.storedScrollItem, true, 0, 0, () => { + this.storedScrollItem = undefined; + }); } ready(): void { + if (!this.scrollUpInitalized) { + this.chatMessagesContainer.nativeElement.classList.add('hide'); + } + setTimeout(() => { this.scrollToBottom(); + // setTimeout(() => { + // this.chatMessagesContainer.nativeElement.classList.remove('hide'); + // }, 100); }); } scrollToBottom(speed?: number): void { - if (this.storedScrollHeight > 0) { - if (this.psChatContent) { - this.psChatContent.update(); + if (!!this.storedScrollItem) { + // if (this.psChatContent) { + // this.psChatContent.update(); - const element = document.getElementById('message-box-readhere'); - if (!!element && this.firstCheckReadHere) { - setTimeout(() => { - this.psChatContent.scrollToTop(element.offsetTop - 200, speed); - }); + const element = document.getElementById('message-box-readhere'); + if (!!element && this.firstCheckReadHere) { + setTimeout(() => { + this.psChatContent.scrollToTop(element.offsetTop - 200, speed); + }); - this.firstCheckReadHere = false; - } else { - setTimeout(() => { - this.psChatContent.scrollToTop( - this.psChatContent.elementRef.nativeElement.scrollHeight - - this.storedScrollHeight, - speed - ); - - this.storedScrollHeight = 0; - }); - } + this.firstCheckReadHere = false; + } else { + this.scrollToStoredItem(); } + // } } else if (this.scrollUpInitalized) { if (!!this.newEventList && this.newEventList.length > 0) { this.existNewMessage.emit( @@ -418,9 +433,23 @@ export class MessagesComponent implements OnInit, OnDestroy { this.firstCheckReadHere = false; } else { - setTimeout(() => { - this.psChatContent.scrollToBottom(0, speed); - }); + this.virtualScroller.scrollInto( + this.eventList[this.eventList.length - 1], + true, + 0, + 0, + () => { + if (!this.scrollUpInitalized) { + this.chatMessagesContainer.nativeElement.classList.remove( + 'hide' + ); + } + } + ); + + // setTimeout(() => { + // this.psChatContent.scrollToBottom(0, speed); + // }); } } } @@ -429,7 +458,7 @@ export class MessagesComponent implements OnInit, OnDestroy { initEventMore() { // 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다. this.scrollUpInitalized = false; - this.storedScrollHeight = 0; + this.storedScrollItem = undefined; } clear() {} @@ -464,9 +493,12 @@ export class MessagesComponent implements OnInit, OnDestroy { event.stopPropagation(); if (this.scrollUpInitalized && this.eventRemained) { - this.storedScrollHeight = this.psChatContent.elementRef.nativeElement.scrollHeight; + // this.storedScrollItem = this.psChatContent.elementRef.nativeElement.scrollHeight; + this.storeScrollPosition(); this.moreEvent.emit(this.eventList[0].seq); + + this.virtualScroller.invalidateCachedMeasurementAtIndex(0); } } @@ -506,6 +538,9 @@ export class MessagesComponent implements OnInit, OnDestroy { } onScrollup(event: any) { + if (!this.eventList || 0 === this.eventList.length) { + return; + } this.scrollUpInitalized = true; this.scrollUp.emit(event); } @@ -516,6 +551,17 @@ export class MessagesComponent implements OnInit, OnDestroy { this.yReachEnd.emit(event); } + onVsChange(event: IPageInfo) { + if ( + -1 === event.startIndex || + -1 === event.endIndex || + (0 === event.startIndex && 0 === event.endIndex) + ) { + return; + } + this.logger.debug('onVsChange', event); + } + trackByEvent(index: number, info: Info): number { return info.seq; } From 243cbf0fc7faf9d9ee48bc52ef76ae1a3e034f27 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Mon, 20 Jan 2020 17:26:38 +0900 Subject: [PATCH 2/2] bug fixed --- .../src/lib/components/messages.component.ts | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.ts b/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.ts index 546f53bd..7c78ab41 100644 --- a/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.ts +++ b/projects/ucap-webmessenger-ui-chat/src/lib/components/messages.component.ts @@ -7,7 +7,8 @@ import { ViewChild, OnDestroy, ChangeDetectionStrategy, - ElementRef + ElementRef, + ChangeDetectorRef } from '@angular/core'; import { @@ -146,6 +147,7 @@ export class MessagesComponent implements OnInit, OnDestroy { constructor( private logger: NGXLogger, private datePipe: DatePipe, + private changeDetectorRef: ChangeDetectorRef, private translateService: TranslateService ) {} @@ -157,12 +159,12 @@ export class MessagesComponent implements OnInit, OnDestroy { this.loginRes = loginRes; }); this.roomInfoSubscription = this.roomInfo$.subscribe(roomInfo => { - this.eventList = undefined; - this.newEventList = undefined; - this.searchedList = undefined; - this.eventInfoStatus = undefined; - this.eventRemained = undefined; - this.userInfos = undefined; + // this.eventList = undefined; + // this.newEventList = undefined; + // this.searchedList = undefined; + // this.eventInfoStatus = undefined; + // this.eventRemained = undefined; + // this.userInfos = undefined; this.initEventMore(); this.roomInfo = roomInfo; @@ -180,6 +182,7 @@ export class MessagesComponent implements OnInit, OnDestroy { } this.eventList = eventList; + this.changeDetectorRef.detectChanges(); if (this.searchingMode) { const baseseq = this.baseEventSeq; @@ -284,6 +287,9 @@ export class MessagesComponent implements OnInit, OnDestroy { } getUnreadCount(message: Info): string | number { + // if (!this.userInfos || 0 === this.userInfos.length) { + // return ''; + // } const unreadCnt = this.userInfos.filter(user => { if (message.senderSeq === user.seq) { // 본인 글은 unreadCount 에 포함하지 않는다. @@ -392,9 +398,6 @@ export class MessagesComponent implements OnInit, OnDestroy { setTimeout(() => { this.scrollToBottom(); - // setTimeout(() => { - // this.chatMessagesContainer.nativeElement.classList.remove('hide'); - // }, 100); }); } @@ -433,23 +436,15 @@ export class MessagesComponent implements OnInit, OnDestroy { this.firstCheckReadHere = false; } else { - this.virtualScroller.scrollInto( - this.eventList[this.eventList.length - 1], + this.virtualScroller.scrollToIndex( + this.eventList.length - 1, true, 0, 0, () => { - if (!this.scrollUpInitalized) { - this.chatMessagesContainer.nativeElement.classList.remove( - 'hide' - ); - } + this.chatMessagesContainer.nativeElement.classList.remove('hide'); } ); - - // setTimeout(() => { - // this.psChatContent.scrollToBottom(0, speed); - // }); } } }