virtual scroll of chat room is implemented
This commit is contained in:
parent
86a9cd3d84
commit
f41c5ae5f0
|
@ -7,7 +7,8 @@ import {
|
||||||
Output,
|
Output,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Inject,
|
Inject,
|
||||||
ChangeDetectorRef
|
ChangeDetectorRef,
|
||||||
|
ChangeDetectionStrategy
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ucapAnimations,
|
ucapAnimations,
|
||||||
|
@ -133,7 +134,8 @@ import { FileProtocolService } from '@ucap-webmessenger/protocol-file';
|
||||||
selector: 'app-layout-messenger-messages',
|
selector: 'app-layout-messenger-messages',
|
||||||
templateUrl: './messages.component.html',
|
templateUrl: './messages.component.html',
|
||||||
styleUrls: ['./messages.component.scss'],
|
styleUrls: ['./messages.component.scss'],
|
||||||
animations: ucapAnimations
|
animations: ucapAnimations,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
@Output()
|
@Output()
|
||||||
|
@ -391,15 +393,15 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.interval = setInterval(() => {
|
|
||||||
if (
|
if (
|
||||||
!!this.roomInfoSubject.value &&
|
!!this.roomInfoSubject.value &&
|
||||||
!!this.roomInfoSubject.value.isTimeRoom
|
!!this.roomInfoSubject.value.isTimeRoom
|
||||||
) {
|
) {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
this.store.dispatch(EventStore.infoIntervalClear({}));
|
this.store.dispatch(EventStore.infoIntervalClear({}));
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (!!this.loginResSubscription) {
|
if (!!this.loginResSubscription) {
|
||||||
|
@ -424,8 +426,10 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.searchEventListProcessingSubscription.unsubscribe();
|
this.searchEventListProcessingSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!!this.interval) {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
// this.readyToReply();
|
// this.readyToReply();
|
||||||
|
@ -1847,7 +1851,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.eventRemainedSubject.value
|
this.eventRemainedSubject.value
|
||||||
) {
|
) {
|
||||||
this.moreSearchProcessing = true;
|
this.moreSearchProcessing = true;
|
||||||
this.chatMessages.storeScrollHeight();
|
this.chatMessages.storeScrollPosition();
|
||||||
|
|
||||||
// Case :: retrieve event infos step by step until include searchtext in event..
|
// Case :: retrieve event infos step by step until include searchtext in event..
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
|
|
|
@ -111,14 +111,14 @@ export class MessageBoxComponent implements OnInit, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.logger.debug(
|
// this.logger.debug(
|
||||||
'offsetHeight' + this.message.seq,
|
// 'offsetHeight' + this.message.seq,
|
||||||
this.mbContainer.nativeElement.offsetHeight
|
// this.mbContainer.nativeElement.offsetHeight
|
||||||
);
|
// );
|
||||||
this.elementRef.nativeElement.style.height = `${this.mbContainer
|
// this.elementRef.nativeElement.style.height = `${this.mbContainer
|
||||||
.nativeElement.offsetHeight + 20}px`;
|
// .nativeElement.offsetHeight + 20}px`;
|
||||||
this.elementRef.nativeElement.style.maxHeight = `${this.mbContainer
|
// this.elementRef.nativeElement.style.maxHeight = `${this.mbContainer
|
||||||
.nativeElement.offsetHeight + 20}px`;
|
// .nativeElement.offsetHeight + 20}px`;
|
||||||
this.mbContainer.nativeElement.classList.remove('hide');
|
this.mbContainer.nativeElement.classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,14 @@
|
||||||
(psYReachStart)="onYReachStart($event)"
|
(psYReachStart)="onYReachStart($event)"
|
||||||
(psYReachEnd)="onYReachEnd($event)"
|
(psYReachEnd)="onYReachEnd($event)"
|
||||||
[enableUnequalChildrenSizes]="true"
|
[enableUnequalChildrenSizes]="true"
|
||||||
|
[modifyOverflowStyleOfParentScroll]="false"
|
||||||
|
(vsChange)="onVsChange($event)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
#chatMessagesContainer
|
||||||
|
class="chat-messages hide"
|
||||||
|
[style.opacity]="fixScreen ? 0 : 1"
|
||||||
>
|
>
|
||||||
<div class="chat-messages" [style.opacity]="fixScreen ? 0 : 1">
|
|
||||||
<div
|
<div
|
||||||
*ngIf="eventRemained && eventList.length > 0"
|
*ngIf="eventRemained && eventList.length > 0"
|
||||||
class="message-row view-previous"
|
class="message-row view-previous"
|
||||||
|
@ -51,17 +57,15 @@
|
||||||
<!-- MESSAGE -->
|
<!-- MESSAGE -->
|
||||||
<div #container>
|
<div #container>
|
||||||
<ucap-chat-message-box
|
<ucap-chat-message-box
|
||||||
*ngFor="
|
*ngFor="let message of scroll.viewPortItems; trackBy: trackByEvent"
|
||||||
let message of scroll.viewPortItems;
|
|
||||||
trackBy: trackByEvent;
|
|
||||||
let i = index
|
|
||||||
"
|
|
||||||
[id]="message.seq"
|
[id]="message.seq"
|
||||||
[message]="message"
|
[message]="message"
|
||||||
[mine]="message.senderSeq === loginRes.userSeq"
|
[mine]="message.senderSeq === loginRes.userSeq"
|
||||||
[highlight]="isHighlightedEvent(message.seq)"
|
[highlight]="isHighlightedEvent(message.seq)"
|
||||||
[existReadToHere]="getReadHere(i) && existReadHere && !clearReadHere"
|
[existReadToHere]="
|
||||||
[dateChanged]="getDateSplitter(i)"
|
getReadHere(message) && existReadHere && !clearReadHere
|
||||||
|
"
|
||||||
|
[dateChanged]="getDateSplitter(message)"
|
||||||
[senderName]="getUserName(message.senderSeq)"
|
[senderName]="getUserName(message.senderSeq)"
|
||||||
[profileImageRoot]="profileImageRoot"
|
[profileImageRoot]="profileImageRoot"
|
||||||
[profileImage]="getUserProfile(message.senderSeq)"
|
[profileImage]="getUserProfile(message.senderSeq)"
|
||||||
|
|
|
@ -185,3 +185,7 @@ $meBox-bg: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import {
|
||||||
Output,
|
Output,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
ElementRef,
|
ChangeDetectionStrategy,
|
||||||
Self
|
ElementRef
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -28,12 +28,13 @@ import { FileDownloadItem } from '@ucap-webmessenger/api';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
|
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
import { VirtualScrollerComponent, IPageInfo } from 'ngx-virtual-scroller';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ucap-chat-messages',
|
selector: 'ucap-chat-messages',
|
||||||
templateUrl: './messages.component.html',
|
templateUrl: './messages.component.html',
|
||||||
styleUrls: ['./messages.component.scss']
|
styleUrls: ['./messages.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class MessagesComponent implements OnInit, OnDestroy {
|
export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
@Input()
|
@Input()
|
||||||
|
@ -103,13 +104,16 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
@Output()
|
@Output()
|
||||||
existNewMessage = new EventEmitter<Info<EventJson>>();
|
existNewMessage = new EventEmitter<Info<EventJson>>();
|
||||||
|
|
||||||
|
@ViewChild('chatMessagesContainer', { static: false })
|
||||||
|
chatMessagesContainer: ElementRef<HTMLElement>;
|
||||||
|
|
||||||
@ViewChild(PerfectScrollbarDirective, { static: false })
|
@ViewChild(PerfectScrollbarDirective, { static: false })
|
||||||
psChatContent: PerfectScrollbarDirective;
|
psChatContent: PerfectScrollbarDirective;
|
||||||
|
|
||||||
@ViewChild(VirtualScrollerComponent, { static: false })
|
@ViewChild(VirtualScrollerComponent, { static: false })
|
||||||
private virtualScroller: VirtualScrollerComponent;
|
private virtualScroller: VirtualScrollerComponent;
|
||||||
|
|
||||||
storedScrollHeight = 0; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩.
|
storedScrollItem: Info<EventJson>; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩.
|
||||||
scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다.
|
scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다.
|
||||||
firstCheckReadHere = true;
|
firstCheckReadHere = true;
|
||||||
initRoomLastEventSeq: number;
|
initRoomLastEventSeq: number;
|
||||||
|
@ -175,6 +179,8 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
this.firstCheckReadHere = false;
|
this.firstCheckReadHere = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.eventList = eventList;
|
||||||
|
|
||||||
if (this.searchingMode) {
|
if (this.searchingMode) {
|
||||||
const baseseq = this.baseEventSeq;
|
const baseseq = this.baseEventSeq;
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
|
@ -187,8 +193,6 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
this.ready();
|
this.ready();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventList = eventList;
|
|
||||||
});
|
});
|
||||||
this.newEventListSubscription = this.newEventList$.subscribe(
|
this.newEventListSubscription = this.newEventList$.subscribe(
|
||||||
newEventList => {
|
newEventList => {
|
||||||
|
@ -310,7 +314,9 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Date Splitter show check */
|
/** Date Splitter show check */
|
||||||
getDateSplitter(curIndex: number): boolean {
|
getDateSplitter(message: Info<EventJson>): boolean {
|
||||||
|
const curIndex = this.eventList.findIndex(v => v.seq === message.seq);
|
||||||
|
|
||||||
if (curIndex === 0) {
|
if (curIndex === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -318,21 +324,15 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
if (!this.eventList[curIndex]) {
|
if (!this.eventList[curIndex]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
return !moment(this.eventList[curIndex].sendDate).isSame(
|
||||||
this.datePipe.transform(
|
moment(this.eventList[curIndex - 1].sendDate),
|
||||||
moment(this.eventList[curIndex].sendDate).toDate(),
|
'day'
|
||||||
'yyyyMMdd'
|
|
||||||
) !==
|
|
||||||
this.datePipe.transform(
|
|
||||||
moment(this.eventList[curIndex - 1].sendDate).toDate(),
|
|
||||||
'yyyyMMdd'
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadHere(messageIndex: number): boolean {
|
getReadHere(message: Info<EventJson>): boolean {
|
||||||
if (
|
if (
|
||||||
!!this.roomInfo &&
|
!!this.roomInfo &&
|
||||||
!!this.roomInfo.lastReadEventSeq &&
|
!!this.roomInfo.lastReadEventSeq &&
|
||||||
|
@ -344,6 +344,10 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
this.roomInfo.roomType === RoomType.Multi
|
this.roomInfo.roomType === RoomType.Multi
|
||||||
) {
|
) {
|
||||||
if (!this.roomInfo.isTimeRoom) {
|
if (!this.roomInfo.isTimeRoom) {
|
||||||
|
const messageIndex = this.eventList.findIndex(
|
||||||
|
v => v.seq === message.seq
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.eventList[messageIndex].seq ===
|
this.eventList[messageIndex].seq ===
|
||||||
this.roomInfo.lastReadEventSeq + 1
|
this.roomInfo.lastReadEventSeq + 1
|
||||||
|
@ -365,20 +369,39 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
return rtnStr;
|
return rtnStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
storeScrollHeight() {
|
storeScrollPosition() {
|
||||||
this.storedScrollHeight = this.psChatContent.elementRef.nativeElement.scrollHeight;
|
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 {
|
ready(): void {
|
||||||
|
if (!this.scrollUpInitalized) {
|
||||||
|
this.chatMessagesContainer.nativeElement.classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
|
// setTimeout(() => {
|
||||||
|
// this.chatMessagesContainer.nativeElement.classList.remove('hide');
|
||||||
|
// }, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom(speed?: number): void {
|
scrollToBottom(speed?: number): void {
|
||||||
if (this.storedScrollHeight > 0) {
|
if (!!this.storedScrollItem) {
|
||||||
if (this.psChatContent) {
|
// if (this.psChatContent) {
|
||||||
this.psChatContent.update();
|
// this.psChatContent.update();
|
||||||
|
|
||||||
const element = document.getElementById('message-box-readhere');
|
const element = document.getElementById('message-box-readhere');
|
||||||
if (!!element && this.firstCheckReadHere) {
|
if (!!element && this.firstCheckReadHere) {
|
||||||
|
@ -388,17 +411,9 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.firstCheckReadHere = false;
|
this.firstCheckReadHere = false;
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
this.scrollToStoredItem();
|
||||||
this.psChatContent.scrollToTop(
|
|
||||||
this.psChatContent.elementRef.nativeElement.scrollHeight -
|
|
||||||
this.storedScrollHeight,
|
|
||||||
speed
|
|
||||||
);
|
|
||||||
|
|
||||||
this.storedScrollHeight = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
} else if (this.scrollUpInitalized) {
|
} else if (this.scrollUpInitalized) {
|
||||||
if (!!this.newEventList && this.newEventList.length > 0) {
|
if (!!this.newEventList && this.newEventList.length > 0) {
|
||||||
this.existNewMessage.emit(
|
this.existNewMessage.emit(
|
||||||
|
@ -418,9 +433,23 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.firstCheckReadHere = false;
|
this.firstCheckReadHere = false;
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
this.virtualScroller.scrollInto(
|
||||||
this.psChatContent.scrollToBottom(0, speed);
|
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() {
|
initEventMore() {
|
||||||
// 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다.
|
// 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다.
|
||||||
this.scrollUpInitalized = false;
|
this.scrollUpInitalized = false;
|
||||||
this.storedScrollHeight = 0;
|
this.storedScrollItem = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {}
|
clear() {}
|
||||||
|
@ -464,9 +493,12 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (this.scrollUpInitalized && this.eventRemained) {
|
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.moreEvent.emit(this.eventList[0].seq);
|
||||||
|
|
||||||
|
this.virtualScroller.invalidateCachedMeasurementAtIndex(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,6 +538,9 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
onScrollup(event: any) {
|
onScrollup(event: any) {
|
||||||
|
if (!this.eventList || 0 === this.eventList.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.scrollUpInitalized = true;
|
this.scrollUpInitalized = true;
|
||||||
this.scrollUp.emit(event);
|
this.scrollUp.emit(event);
|
||||||
}
|
}
|
||||||
|
@ -516,6 +551,17 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
this.yReachEnd.emit(event);
|
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<EventJson>): number {
|
trackByEvent(index: number, info: Info<EventJson>): number {
|
||||||
return info.seq;
|
return info.seq;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user