virtual scroll of chat room

This commit is contained in:
Richard Park 2020-01-17 10:41:22 +09:00
parent 98a067e889
commit 9bc416cce3
11 changed files with 774 additions and 497 deletions

34
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ucap-webmessenger",
"version": "0.0.4",
"version": "0.0.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -349,6 +349,15 @@
}
}
},
"@angular/cdk-experimental": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/@angular/cdk-experimental/-/cdk-experimental-8.2.3.tgz",
"integrity": "sha512-pGWLh+njlSK2HavyES1g9xZQXmnqj9HJalL+EJii1gLEyvEatdJfsBxidxvdxrXaIYrY0ZfkiG2qW/4wOOKRHA==",
"dev": true,
"requires": {
"tslib": "^1.7.1"
}
},
"@angular/cli": {
"version": "8.3.22",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.22.tgz",
@ -3272,6 +3281,12 @@
"defer-to-connect": "^1.0.1"
}
},
"@tweenjs/tween.js": {
"version": "17.4.0",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.4.0.tgz",
"integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==",
"dev": true
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -3479,6 +3494,12 @@
"integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==",
"dev": true
},
"@types/tween.js": {
"version": "17.2.0",
"resolved": "https://registry.npmjs.org/@types/tween.js/-/tween.js-17.2.0.tgz",
"integrity": "sha512-mOsqurEtFEzwgkVc/jDVE2XrjZBYTbrmDUyCr9GXmnfc6q5otokxFtKvSY/B21zgz9LVRIvRTawKczjKi57wrA==",
"dev": true
},
"@types/uglify-js": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
@ -11369,6 +11390,17 @@
"resize-observer-polyfill": "^1.5.0"
}
},
"ngx-virtual-scroller": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ngx-virtual-scroller/-/ngx-virtual-scroller-3.0.3.tgz",
"integrity": "sha512-00au5zpcPMzbci9VmM9QlOT6ZQ+NqgpWaXsM2Y7dg6SSlCDcFMW8nMxoMsogP2b2mrBLpIIEB91nVtwLnwWs4A==",
"dev": true,
"requires": {
"@tweenjs/tween.js": "17.4.0",
"@types/tween.js": "17.2.0",
"tslib": "^1.9.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@ -59,6 +59,7 @@
"@angular-devkit/build-ng-packagr": "^0.803.22",
"@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/cdk-experimental": "^8.2.3",
"@angular/cli": "^8.3.22",
"@angular/common": "^8.2.14",
"@angular/compiler": "^8.2.14",
@ -134,6 +135,7 @@
"ngrx-store-freeze": "^0.2.4",
"ngx-logger": "^4.0.8",
"ngx-perfect-scrollbar": "^8.0.0",
"ngx-virtual-scroller": "^3.0.3",
"npm-run-all": "^4.1.5",
"parallel-webpack": "^2.4.0",
"protractor": "~5.4.0",

View File

@ -42,18 +42,22 @@
mat-icon-button
aria-label="chats button"
class="responsive-chats-button chat-timer"
*ngIf="!!roomInfo && roomInfo.isTimeRoom"
*ngIf="!!roomInfoSubject.value && roomInfoSubject.value.isTimeRoom"
>
<mat-icon>timer</mat-icon>
</button>
</div>
<div class="room-info">
<h3 class="room-name">
<ng-container *ngIf="!roomInfo || !userInfoList">
<ng-container
*ngIf="!roomInfoSubject.value || !userInfoListSubject.value"
>
{{ 'chat.getRoomNameInProgress' | translate }}
</ng-container>
<ng-container *ngIf="!!roomInfo && !!userInfoList">
<ng-container [ngSwitch]="roomInfo.roomType">
<ng-container
*ngIf="!!roomInfoSubject.value && !!userInfoListSubject.value"
>
<ng-container [ngSwitch]="roomInfoSubject.value.roomType">
<ng-container *ngSwitchCase="RoomType.Mytalk">
MyTalk
</ng-container>
@ -70,11 +74,12 @@
<ng-container *ngSwitchDefault>
<ng-template
[ngIf]="
!!roomInfo.roomName && '' !== roomInfo.roomName.trim()
!!roomInfoSubject.value.roomName &&
'' !== roomInfoSubject.value.roomName.trim()
"
[ngIfElse]="roomNameNotExist"
>
{{ roomInfo.roomName }}
{{ roomInfoSubject.value.roomName }}
</ng-template>
<ng-template #roomNameNotExist>
{{ _roomUserInfos | ucapTranslate: 'name':',' }}
@ -85,31 +90,33 @@
</h3>
<!-- Timer Room Info -->
<div
*ngIf="roomInfo && roomInfo.isTimeRoom"
*ngIf="roomInfoSubject.value && roomInfoSubject.value.isTimeRoom"
class="room-type text-accent-color "
>
<span class="bg-accent-darkest"
>{{ getConvertTimer(roomInfo.timeRoomInterval) }} </span
>{{
getConvertTimer(roomInfoSubject.value.timeRoomInterval)
}} </span
>{{ 'chat.isRoomTypeSecret' | translate }}
</div>
<!-- Timer Room Info -->
</div>
<div class="room-option">
<button
*ngIf="!!roomInfo"
*ngIf="!!roomInfoSubject.value"
mat-icon-button
(click)="onClickReceiveAlarm()"
aria-label="Toggle Receive Alarm"
>
<mat-icon
class="amber-fg"
*ngIf="roomInfo.receiveAlarm"
*ngIf="roomInfoSubject.value.receiveAlarm"
matTooltip="{{ 'chat.notificationIsOn' | translate }}"
>notifications_active</mat-icon
>
<mat-icon
class="secondary-text"
*ngIf="!roomInfo.receiveAlarm"
*ngIf="!roomInfoSubject.value.receiveAlarm"
matTooltip="{{ 'chat.notificationIsOff' | translate }}"
>notifications_off</mat-icon
>
@ -218,29 +225,25 @@
(fileDragLeave)="onFileDragLeave()"
>
<!-- CHAT MESSAGES -->
<perfect-scrollbar
fxFlex="1 1 auto"
#psChatContent
(psScrollUp)="onScrollup($event)"
(psYReachStart)="onScrollReachStart($event)"
(psYReachEnd)="onScrollReachEnd($event)"
>
<ucap-chat-messages
[eventList]="eventList"
[searchedList]="searchedList"
[roomInfo]="roomInfo"
[eventInfoStatus]="eventInfoStatus"
[eventRemain]="eventRemain$ | async"
[userInfos]="userInfoList"
[loginRes]="loginRes"
#chatMessages
[eventList$]="eventListSubject.asObservable()"
[newEventList$]="eventListNewSubject.asObservable()"
[searchedList$]="searchedListSubject.asObservable()"
[roomInfo$]="roomInfoSubject.asObservable()"
[eventInfoStatus$]="eventInfoStatusSubject.asObservable()"
[eventRemained$]="eventRemainedSubject.asObservable()"
[userInfos$]="userInfoListSubject.asObservable()"
[loginRes$]="loginResSubject.asObservable()"
[sessionVerInfo]="sessionVerInfo"
[isShowUnreadCount]="getShowUnreadCount()"
[clearReadHere]="clearReadHere"
[minShowReadHere]="
environment.productConfig.CommonSetting.readHereShowMinimumEventCount
"
[initRoomLastEventSeq]="initRoomLastEventSeq"
[translationSimpleview]="translationSimpleview"
[searchingMode]="moreSearchProcessing"
(moreEvent)="onMoreEvent($event)"
(massDetail)="onMassDetail($event)"
(massTranslationDetail)="onMassTranslationDetail($event)"
@ -248,9 +251,13 @@
(fileViewer)="onFileViewer($event)"
(contextMenu)="onContextMenuMessage($event)"
(openProfile)="onClickOpenProfile($event)"
(scrollUp)="onScrollupMessages($event)"
(yReachStart)="onYReachStartMessages($event)"
(yReachEnd)="onYReachEndMessages($event)"
(existNewMessage)="onExistNewMessage($event)"
>
</ucap-chat-messages>
</perfect-scrollbar>
<!-- CHAT MESSAGES -->
<div class="file-drop-zone-container">
<ucap-file-upload-queue
@ -319,9 +326,7 @@
#messageContextMenuTrigger="matMenuTrigger"
[matMenuTriggerFor]="messageContextMenu"
></div>
<mat-menu
#messageContextMenu="matMenu"
>
<mat-menu #messageContextMenu="matMenu">
<ng-template matMenuContent let-message="message" let-clicktype="clicktype">
<ng-container *ngIf="!isRecalledMessage(message.type)">
<button
@ -333,14 +338,14 @@
</button>
<button
mat-menu-item
*ngIf="isForwardableMessage(message, eventInfoStatus)"
*ngIf="isForwardableMessage(message, eventInfoStatusSubject.value)"
(click)="onClickMessageContextMenu('REPLAY', message)"
>
{{ 'chat.forwardEventTo' | translate }}
</button>
<button
mat-menu-item
*ngIf="isForwardableMessage(message, eventInfoStatus)"
*ngIf="isForwardableMessage(message, eventInfoStatusSubject.value)"
(click)="onClickMessageContextMenu('REPLAY_TO_ME', message)"
>
{{ 'chat.forwardEventToMe' | translate }}
@ -353,7 +358,7 @@
</button>
<button
mat-menu-item
*ngIf="isRecallableMessage(message, loginRes.userSeq)"
*ngIf="isRecallableMessage(message, loginResSubject.value.userSeq)"
(click)="onClickMessageContextMenu('RECALL', message)"
>
{{ 'chat.recallEvent' | translate }}

View File

@ -5,7 +5,7 @@
[ngClass]="{
me: mine,
contact: !mine,
searched: searched
highlight: highlight
}"
>
<ucap-chat-message-box-read-here

View File

@ -87,7 +87,7 @@ $meBox-bg: #ffffff;
text-align: end;
}
}
&.searched {
&.highlight {
.bubble {
color: red;
}

View File

@ -35,7 +35,7 @@ export class MessageBoxComponent implements OnInit, AfterViewInit {
mine = false;
@Input()
searched = false;
highlight = false;
@Input()
existReadToHere = false;
@ -113,10 +113,12 @@ export class MessageBoxComponent implements OnInit, AfterViewInit {
ngAfterViewInit(): void {
this.logger.debug(
'offsetHeight' + this.message.seq,
this.elementRef.nativeElement.offsetHeight
this.mbContainer.nativeElement.offsetHeight
);
this.elementRef.nativeElement.style.height = `${this.elementRef.nativeElement.offsetHeight}px`;
this.elementRef.nativeElement.style.maxHeight = `${this.elementRef.nativeElement.offsetHeight}px`;
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');
}

View File

@ -1,6 +1,17 @@
<div class="chat-messages" #scrollMe>
<virtual-scroller
#scroll
[items]="eventList"
perfectScrollbar
fxFlexFill
#psChatContent
(psScrollUp)="onScrollup($event)"
(psYReachStart)="onYReachStart($event)"
(psYReachEnd)="onYReachEnd($event)"
[enableUnequalChildrenSizes]="true"
>
<div class="chat-messages" #scrollMe>
<div
*ngIf="eventRemain && messages.length > 0"
*ngIf="eventRemained && eventList.length > 0"
class="message-row view-previous"
>
<button mat-button (click)="onClickMore($event)" class="bg-accent-dark">
@ -25,8 +36,8 @@
class="unRead-count"
*ngIf="
!!roomInfo &&
!!firstEventSeq &&
roomInfo.lastReadEventSeq < firstEventSeq
!!baseEventSeq &&
roomInfo.lastReadEventSeq < baseEventSeq
"
>
<span class="line"></span>
@ -37,12 +48,17 @@
</div>
<!-- MESSAGE -->
<div #container>
<ucap-chat-message-box
*ngFor="let message of messages; let i = index"
*ngFor="
let message of scroll.viewPortItems;
trackBy: trackByEvent;
let i = index
"
[id]="message.seq"
[message]="message"
[mine]="message.senderSeq === loginRes.userSeq"
[searched]="getEventSearched(message.seq)"
[highlight]="isHighlightedEvent(message.seq)"
[existReadToHere]="getReadHere(i) && existReadHere && !clearReadHere"
[dateChanged]="getDateSplitter(i)"
[senderName]="getUserName(message.senderSeq)"
@ -59,4 +75,6 @@
(contextMenu)="onContextMenu($event)"
>
</ucap-chat-message-box>
</div>
</div>
</div>
</virtual-scroller>

View File

@ -1,4 +1,12 @@
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import {
Component,
OnInit,
Input,
EventEmitter,
Output,
ViewChild,
OnDestroy
} from '@angular/core';
import {
Info,
@ -12,40 +20,39 @@ import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { UserInfo, RoomInfo, RoomType } from '@ucap-webmessenger/protocol-room';
import { NGXLogger } from 'ngx-logger';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { FileInfo } from '../models/file-info.json';
import { DatePipe } from '@angular/common';
import moment from 'moment';
import { FileDownloadItem } from '@ucap-webmessenger/api';
import { TranslateService } from '@ngx-translate/core';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { Observable, Subscription } from 'rxjs';
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
@Component({
selector: 'ucap-chat-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.scss']
})
export class MessagesComponent implements OnInit {
export class MessagesComponent implements OnInit, OnDestroy {
@Input()
loginRes: LoginResponse;
loginRes$: Observable<LoginResponse>;
@Input()
roomInfo: RoomInfo;
roomInfo$: Observable<RoomInfo>;
@Input()
set eventList(elist: Info<EventJson>[]) {
if (!!elist && elist.length > 0) {
this.firstEventSeq = elist[0].seq;
}
this.messages = elist;
}
eventList$: Observable<Info<EventJson>[]>;
@Input()
searchedList: Info<EventJson>[];
newEventList$: Observable<Info<EventJson>[]>;
@Input()
eventInfoStatus?: InfoResponse;
searchedList$: Observable<Info<EventJson>[]>;
@Input()
eventRemain: boolean;
eventInfoStatus$: Observable<InfoResponse>;
@Input()
userInfos?: UserInfo[];
eventRemained$: Observable<boolean>;
@Input()
sessionVerInfo: VersionInfo2Response;
@Input()
userInfos$: Observable<UserInfo[]>;
@Input()
isShowUnreadCount = true;
@Input()
@ -53,9 +60,9 @@ export class MessagesComponent implements OnInit {
@Input()
minShowReadHere = 10;
@Input()
initRoomLastEventSeq: number;
@Input()
translationSimpleview = false;
@Input()
searchingMode = false;
@Output()
openProfile = new EventEmitter<number>();
@ -83,13 +90,50 @@ export class MessagesComponent implements OnInit {
type?: string;
}>();
messages: Info<EventJson>[];
@Output()
scrollUp = new EventEmitter<any>();
@Output()
yReachEnd = new EventEmitter<any>();
@Output()
yReachStart = new EventEmitter<any>();
@Output()
existNewMessage = new EventEmitter<Info<EventJson>>();
@ViewChild(PerfectScrollbarDirective, { static: false })
psChatContent?: PerfectScrollbarDirective;
@ViewChild(VirtualScrollerComponent, { static: false })
private virtualScroller: VirtualScrollerComponent;
storedScrollHeight = 0; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩.
scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다.
firstCheckReadHere = true;
initRoomLastEventSeq: number;
baseEventSeq = 0;
loginRes: LoginResponse;
loginResSubscription: Subscription;
roomInfo: RoomInfo;
roomInfoSubscription: Subscription;
eventList: Info<EventJson>[];
eventListSubscription: Subscription;
newEventList: Info<EventJson>[];
newEventListSubscription: Subscription;
searchedList: Info<EventJson>[];
searchedListSubscription: Subscription;
eventInfoStatus: InfoResponse;
eventInfoStatusSubscription: Subscription;
eventRemained: boolean;
eventRemainedSubscription: Subscription;
userInfos: UserInfo[];
userInfosSubscription: Subscription;
EventType = EventType;
profileImageRoot: string;
moment = moment;
firstEventSeq = 0;
existReadHere = false;
constructor(
@ -101,6 +145,91 @@ export class MessagesComponent implements OnInit {
ngOnInit() {
this.profileImageRoot =
this.profileImageRoot || this.sessionVerInfo.profileRoot;
this.loginResSubscription = this.loginRes$.subscribe(loginRes => {
this.loginRes = loginRes;
});
this.roomInfoSubscription = this.roomInfo$.subscribe(roomInfo => {
this.initEventMore();
this.roomInfo = roomInfo;
});
this.eventListSubscription = this.eventList$.subscribe(eventList => {
if (
!!eventList &&
eventList.length > 0 &&
!!this.roomInfo &&
!!this.roomInfo.lastReadEventSeq &&
this.baseEventSeq <= this.roomInfo.lastReadEventSeq
) {
// 조회된 내용중에 read here 가 있을 경우.
this.firstCheckReadHere = false;
}
if (this.searchingMode) {
const baseseq = this.baseEventSeq;
// setTimeout(() => {
// this.doSearchTextInEvent(this.searchText, baseseq);
// }, 800);
this.baseEventSeq = eventList[0].seq;
} else {
if (!!eventList && eventList.length > 0) {
this.baseEventSeq = eventList[0].seq;
this.ready();
}
}
this.eventList = eventList;
});
this.newEventListSubscription = this.newEventList$.subscribe(
newEventList => {
this.newEventList = newEventList;
}
);
this.searchedListSubscription = this.searchedList$.subscribe(
searchedList => {
this.searchedList = searchedList;
}
);
this.eventInfoStatusSubscription = this.eventInfoStatus$.subscribe(
eventInfoStatus => {
this.eventInfoStatus = eventInfoStatus;
}
);
this.eventRemainedSubscription = this.eventRemained$.subscribe(
eventRemained => {
this.eventRemained = eventRemained;
}
);
this.userInfosSubscription = this.userInfos$.subscribe(userInfos => {
this.userInfos = userInfos;
});
}
ngOnDestroy(): void {
if (!!this.loginResSubscription) {
this.loginResSubscription.unsubscribe();
}
if (!!this.roomInfoSubscription) {
this.roomInfoSubscription.unsubscribe();
}
if (!!this.eventListSubscription) {
this.eventListSubscription.unsubscribe();
}
if (!!this.newEventListSubscription) {
this.newEventListSubscription.unsubscribe();
}
if (!!this.searchedListSubscription) {
this.searchedListSubscription.unsubscribe();
}
if (!!this.eventInfoStatusSubscription) {
this.eventInfoStatusSubscription.unsubscribe();
}
if (!!this.eventRemainedSubscription) {
this.eventRemainedSubscription.unsubscribe();
}
if (!!this.userInfosSubscription) {
this.userInfosSubscription.unsubscribe();
}
}
/**
@ -132,7 +261,8 @@ export class MessagesComponent implements OnInit {
}
return '';
}
getEventSearched(seq: number): boolean {
isHighlightedEvent(seq: number): boolean {
return (
!!this.searchedList &&
this.searchedList.filter(event => event.seq === seq).length > 0
@ -177,11 +307,11 @@ export class MessagesComponent implements OnInit {
if (curIndex > 0) {
return (
this.datePipe.transform(
moment(this.messages[curIndex].sendDate).toDate(),
moment(this.eventList[curIndex].sendDate).toDate(),
'yyyyMMdd'
) !==
this.datePipe.transform(
moment(this.messages[curIndex - 1].sendDate).toDate(),
moment(this.eventList[curIndex - 1].sendDate).toDate(),
'yyyyMMdd'
)
);
@ -202,7 +332,7 @@ export class MessagesComponent implements OnInit {
) {
if (!this.roomInfo.isTimeRoom) {
if (
this.messages[messageIndex].seq ===
this.eventList[messageIndex].seq ===
this.roomInfo.lastReadEventSeq + 1
) {
this.existReadHere = true;
@ -217,11 +347,99 @@ export class MessagesComponent implements OnInit {
getStringReadHereMore(): string {
let rtnStr = '';
rtnStr = this.translateService.instant('chat.event.moreUnreadEventsWith', {
countOfUnread: this.firstEventSeq - (this.roomInfo.lastReadEventSeq + 1)
countOfUnread: this.baseEventSeq - (this.roomInfo.lastReadEventSeq + 1)
});
return rtnStr;
}
storeScrollHeight() {
this.storedScrollHeight = this.psChatContent.elementRef.nativeElement.scrollHeight;
}
ready(): void {
setTimeout(() => {
this.scrollToBottom();
});
}
scrollToBottom(speed?: number): void {
if (this.storedScrollHeight > 0) {
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);
});
this.firstCheckReadHere = false;
} else {
setTimeout(() => {
this.psChatContent.scrollToTop(
this.psChatContent.elementRef.nativeElement.scrollHeight -
this.storedScrollHeight,
speed
);
this.storedScrollHeight = 0;
});
}
}
} else if (this.scrollUpInitalized) {
if (!!this.newEventList && this.newEventList.length > 0) {
this.existNewMessage.emit(
this.newEventList[this.newEventList.length - 1]
);
}
} else {
speed = speed || 0;
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);
});
this.firstCheckReadHere = false;
} else {
setTimeout(() => {
this.psChatContent.scrollToBottom(0, speed);
});
}
}
}
}
initEventMore() {
// 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다.
this.scrollUpInitalized = false;
this.storedScrollHeight = 0;
}
clear() {}
gotoPosition(eventSeq: number) {
// if (this.psChatContent) {
// this.psChatContent.update();
// const element = document.getElementById(eventSeq.toString());
// if (!!element) {
// setTimeout(() => {
// this.psChatContent.scrollToTop(element.offsetTop - 200);
// });
// }
// }
if (!!this.virtualScroller) {
const e = this.eventList.find(v => v.seq === eventSeq);
this.virtualScroller.scrollDebounceTime = 0;
this.virtualScroller.scrollInto(e);
}
}
onClickOpenProfile(userSeq: number) {
this.openProfile.emit(userSeq);
}
@ -230,7 +448,11 @@ export class MessagesComponent implements OnInit {
event.preventDefault();
event.stopPropagation();
this.moreEvent.emit(this.messages[0].seq);
if (this.scrollUpInitalized && this.eventRemained) {
this.storedScrollHeight = this.psChatContent.elementRef.nativeElement.scrollHeight;
this.moreEvent.emit(this.eventList[0].seq);
}
}
/** [Event] MassTalk Detail View */
@ -267,4 +489,19 @@ export class MessagesComponent implements OnInit {
}) {
this.contextMenu.emit(event);
}
onScrollup(event: any) {
this.scrollUpInitalized = true;
this.scrollUp.emit(event);
}
onYReachStart(event: any) {
this.yReachStart.emit(event);
}
onYReachEnd(event: any) {
this.yReachEnd.emit(event);
}
trackByEvent(index: number, info: Info<EventJson>): number {
return info.seq;
}
}

View File

@ -4,12 +4,19 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { ScrollingModule as ExperimentalScrollingModule } from '@angular/cdk-experimental/scrolling';
import { MatTooltipModule } from '@angular/material';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
import { VirtualScrollerModule } from 'ngx-virtual-scroller';
import { TranslateModule } from '@ngx-translate/core';
import { UCapUiModule } from '@ucap-webmessenger/ui';
@ -36,7 +43,6 @@ import { TranslationComponent as MBTranslationComponent } from './components/mes
import { VideoComponent as MBVideoComponent } from './components/message-box/video.component';
import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component';
import { AllimComponent as MBAllimComponent } from './components/message-box/allim.component';
import { MatTooltipModule } from '@angular/material';
const COMPONENTS = [
FormComponent,
@ -71,6 +77,10 @@ const PROVIDERS = [DatePipe];
FormsModule,
ReactiveFormsModule,
FlexLayoutModule,
ScrollingModule,
ExperimentalScrollingModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
@ -78,6 +88,9 @@ const PROVIDERS = [DatePipe];
MatMenuModule,
MatTooltipModule,
PerfectScrollbarModule,
VirtualScrollerModule,
TranslateModule,
UCapUiModule

View File

@ -19,7 +19,9 @@ export * from './lib/components/message-box/video-conference.component';
export * from './lib/components/message-box/video.component';
export * from './lib/components/form.component';
export * from './lib/components/message-box.component';
export * from './lib/components/messages.component';
export * from './lib/components/search.component';
export * from './lib/models/file-info.json';
export * from './lib/models/mass-talk-info.json';