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", "name": "ucap-webmessenger",
"version": "0.0.4", "version": "0.0.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "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": { "@angular/cli": {
"version": "8.3.22", "version": "8.3.22",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.22.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.22.tgz",
@ -3272,6 +3281,12 @@
"defer-to-connect": "^1.0.1" "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": { "@types/anymatch": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -3479,6 +3494,12 @@
"integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==", "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==",
"dev": true "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": { "@types/uglify-js": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
@ -11369,6 +11390,17 @@
"resize-observer-polyfill": "^1.5.0" "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": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "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-devkit/build-ng-packagr": "^0.803.22",
"@angular/animations": "^8.2.14", "@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3", "@angular/cdk": "^8.2.3",
"@angular/cdk-experimental": "^8.2.3",
"@angular/cli": "^8.3.22", "@angular/cli": "^8.3.22",
"@angular/common": "^8.2.14", "@angular/common": "^8.2.14",
"@angular/compiler": "^8.2.14", "@angular/compiler": "^8.2.14",
@ -134,6 +135,7 @@
"ngrx-store-freeze": "^0.2.4", "ngrx-store-freeze": "^0.2.4",
"ngx-logger": "^4.0.8", "ngx-logger": "^4.0.8",
"ngx-perfect-scrollbar": "^8.0.0", "ngx-perfect-scrollbar": "^8.0.0",
"ngx-virtual-scroller": "^3.0.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"parallel-webpack": "^2.4.0", "parallel-webpack": "^2.4.0",
"protractor": "~5.4.0", "protractor": "~5.4.0",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,17 @@
<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 class="chat-messages" #scrollMe>
<div <div
*ngIf="eventRemain && messages.length > 0" *ngIf="eventRemained && eventList.length > 0"
class="message-row view-previous" class="message-row view-previous"
> >
<button mat-button (click)="onClickMore($event)" class="bg-accent-dark"> <button mat-button (click)="onClickMore($event)" class="bg-accent-dark">
@ -25,8 +36,8 @@
class="unRead-count" class="unRead-count"
*ngIf=" *ngIf="
!!roomInfo && !!roomInfo &&
!!firstEventSeq && !!baseEventSeq &&
roomInfo.lastReadEventSeq < firstEventSeq roomInfo.lastReadEventSeq < baseEventSeq
" "
> >
<span class="line"></span> <span class="line"></span>
@ -37,12 +48,17 @@
</div> </div>
<!-- MESSAGE --> <!-- MESSAGE -->
<div #container>
<ucap-chat-message-box <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" [id]="message.seq"
[message]="message" [message]="message"
[mine]="message.senderSeq === loginRes.userSeq" [mine]="message.senderSeq === loginRes.userSeq"
[searched]="getEventSearched(message.seq)" [highlight]="isHighlightedEvent(message.seq)"
[existReadToHere]="getReadHere(i) && existReadHere && !clearReadHere" [existReadToHere]="getReadHere(i) && existReadHere && !clearReadHere"
[dateChanged]="getDateSplitter(i)" [dateChanged]="getDateSplitter(i)"
[senderName]="getUserName(message.senderSeq)" [senderName]="getUserName(message.senderSeq)"
@ -60,3 +76,5 @@
> >
</ucap-chat-message-box> </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 { import {
Info, Info,
@ -12,40 +20,39 @@ import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { UserInfo, RoomInfo, RoomType } from '@ucap-webmessenger/protocol-room'; import { UserInfo, RoomInfo, RoomType } from '@ucap-webmessenger/protocol-room';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { FileInfo } from '../models/file-info.json';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import moment from 'moment'; import moment from 'moment';
import { FileDownloadItem } from '@ucap-webmessenger/api'; 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 { Observable, Subscription } from 'rxjs';
import { VirtualScrollerComponent } 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']
}) })
export class MessagesComponent implements OnInit { export class MessagesComponent implements OnInit, OnDestroy {
@Input() @Input()
loginRes: LoginResponse; loginRes$: Observable<LoginResponse>;
@Input() @Input()
roomInfo: RoomInfo; roomInfo$: Observable<RoomInfo>;
@Input() @Input()
set eventList(elist: Info<EventJson>[]) { eventList$: Observable<Info<EventJson>[]>;
if (!!elist && elist.length > 0) {
this.firstEventSeq = elist[0].seq;
}
this.messages = elist;
}
@Input() @Input()
searchedList: Info<EventJson>[]; newEventList$: Observable<Info<EventJson>[]>;
@Input() @Input()
eventInfoStatus?: InfoResponse; searchedList$: Observable<Info<EventJson>[]>;
@Input() @Input()
eventRemain: boolean; eventInfoStatus$: Observable<InfoResponse>;
@Input() @Input()
userInfos?: UserInfo[]; eventRemained$: Observable<boolean>;
@Input() @Input()
sessionVerInfo: VersionInfo2Response; sessionVerInfo: VersionInfo2Response;
@Input()
userInfos$: Observable<UserInfo[]>;
@Input() @Input()
isShowUnreadCount = true; isShowUnreadCount = true;
@Input() @Input()
@ -53,9 +60,9 @@ export class MessagesComponent implements OnInit {
@Input() @Input()
minShowReadHere = 10; minShowReadHere = 10;
@Input() @Input()
initRoomLastEventSeq: number;
@Input()
translationSimpleview = false; translationSimpleview = false;
@Input()
searchingMode = false;
@Output() @Output()
openProfile = new EventEmitter<number>(); openProfile = new EventEmitter<number>();
@ -83,13 +90,50 @@ export class MessagesComponent implements OnInit {
type?: string; 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; EventType = EventType;
profileImageRoot: string; profileImageRoot: string;
moment = moment; moment = moment;
firstEventSeq = 0;
existReadHere = false; existReadHere = false;
constructor( constructor(
@ -101,6 +145,91 @@ export class MessagesComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.profileImageRoot = this.profileImageRoot =
this.profileImageRoot || this.sessionVerInfo.profileRoot; 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 ''; return '';
} }
getEventSearched(seq: number): boolean {
isHighlightedEvent(seq: number): boolean {
return ( return (
!!this.searchedList && !!this.searchedList &&
this.searchedList.filter(event => event.seq === seq).length > 0 this.searchedList.filter(event => event.seq === seq).length > 0
@ -177,11 +307,11 @@ export class MessagesComponent implements OnInit {
if (curIndex > 0) { if (curIndex > 0) {
return ( return (
this.datePipe.transform( this.datePipe.transform(
moment(this.messages[curIndex].sendDate).toDate(), moment(this.eventList[curIndex].sendDate).toDate(),
'yyyyMMdd' 'yyyyMMdd'
) !== ) !==
this.datePipe.transform( this.datePipe.transform(
moment(this.messages[curIndex - 1].sendDate).toDate(), moment(this.eventList[curIndex - 1].sendDate).toDate(),
'yyyyMMdd' 'yyyyMMdd'
) )
); );
@ -202,7 +332,7 @@ export class MessagesComponent implements OnInit {
) { ) {
if (!this.roomInfo.isTimeRoom) { if (!this.roomInfo.isTimeRoom) {
if ( if (
this.messages[messageIndex].seq === this.eventList[messageIndex].seq ===
this.roomInfo.lastReadEventSeq + 1 this.roomInfo.lastReadEventSeq + 1
) { ) {
this.existReadHere = true; this.existReadHere = true;
@ -217,11 +347,99 @@ export class MessagesComponent implements OnInit {
getStringReadHereMore(): string { getStringReadHereMore(): string {
let rtnStr = ''; let rtnStr = '';
rtnStr = this.translateService.instant('chat.event.moreUnreadEventsWith', { rtnStr = this.translateService.instant('chat.event.moreUnreadEventsWith', {
countOfUnread: this.firstEventSeq - (this.roomInfo.lastReadEventSeq + 1) countOfUnread: this.baseEventSeq - (this.roomInfo.lastReadEventSeq + 1)
}); });
return rtnStr; 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) { onClickOpenProfile(userSeq: number) {
this.openProfile.emit(userSeq); this.openProfile.emit(userSeq);
} }
@ -230,7 +448,11 @@ export class MessagesComponent implements OnInit {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); 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 */ /** [Event] MassTalk Detail View */
@ -267,4 +489,19 @@ export class MessagesComponent implements OnInit {
}) { }) {
this.contextMenu.emit(event); 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 { 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 { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu'; 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 { TranslateModule } from '@ngx-translate/core';
import { UCapUiModule } from '@ucap-webmessenger/ui'; 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 { VideoComponent as MBVideoComponent } from './components/message-box/video.component';
import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component'; import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component';
import { AllimComponent as MBAllimComponent } from './components/message-box/allim.component'; import { AllimComponent as MBAllimComponent } from './components/message-box/allim.component';
import { MatTooltipModule } from '@angular/material';
const COMPONENTS = [ const COMPONENTS = [
FormComponent, FormComponent,
@ -71,6 +77,10 @@ const PROVIDERS = [DatePipe];
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
FlexLayoutModule, FlexLayoutModule,
ScrollingModule,
ExperimentalScrollingModule,
MatFormFieldModule, MatFormFieldModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
@ -78,6 +88,9 @@ const PROVIDERS = [DatePipe];
MatMenuModule, MatMenuModule,
MatTooltipModule, MatTooltipModule,
PerfectScrollbarModule,
VirtualScrollerModule,
TranslateModule, TranslateModule,
UCapUiModule 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/message-box/video.component';
export * from './lib/components/form.component'; export * from './lib/components/form.component';
export * from './lib/components/message-box.component';
export * from './lib/components/messages.component'; export * from './lib/components/messages.component';
export * from './lib/components/search.component';
export * from './lib/models/file-info.json'; export * from './lib/models/file-info.json';
export * from './lib/models/mass-talk-info.json'; export * from './lib/models/mass-talk-info.json';