700 lines
19 KiB
TypeScript
700 lines
19 KiB
TypeScript
import {
|
|
Component,
|
|
OnInit,
|
|
Input,
|
|
EventEmitter,
|
|
Output,
|
|
ViewChild,
|
|
OnDestroy,
|
|
ChangeDetectionStrategy,
|
|
ElementRef,
|
|
ChangeDetectorRef,
|
|
ViewChildren,
|
|
QueryList
|
|
} from '@angular/core';
|
|
|
|
import {
|
|
Info,
|
|
EventType,
|
|
InfoResponse,
|
|
EventJson,
|
|
FileEventJson,
|
|
MassTranslationEventJson
|
|
} from '@ucap-webmessenger/protocol-event';
|
|
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 moment from 'moment';
|
|
import { FileDownloadItem } from '@ucap-webmessenger/api';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { Observable, Subscription, timer } from 'rxjs';
|
|
import { VirtualScrollerComponent, IPageInfo } from 'ngx-virtual-scroller';
|
|
import { MessageBoxComponent } from './message-box.component';
|
|
import { debounce } from 'rxjs/operators';
|
|
import { SelectFileInfo } from '@ucap-webmessenger/ui';
|
|
|
|
@Component({
|
|
selector: 'ucap-chat-messages',
|
|
templateUrl: './messages.component.html',
|
|
styleUrls: ['./messages.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
})
|
|
export class MessagesComponent implements OnInit, OnDestroy {
|
|
@Input()
|
|
loginRes$: Observable<LoginResponse>;
|
|
@Input()
|
|
roomInfo$: Observable<RoomInfo>;
|
|
@Input()
|
|
eventList$: Observable<Info<EventJson>[]>;
|
|
@Input()
|
|
newEventList$: Observable<Info<EventJson>[]>;
|
|
@Input()
|
|
searchedList$: Observable<Info<EventJson>[]>;
|
|
@Input()
|
|
eventInfoStatus$: Observable<InfoResponse>;
|
|
@Input()
|
|
eventRemained$: Observable<boolean>;
|
|
@Input()
|
|
sessionVerInfo: VersionInfo2Response;
|
|
@Input()
|
|
userInfos$: Observable<UserInfo[]>;
|
|
|
|
@Input()
|
|
lock$: Observable<boolean>;
|
|
|
|
@Input()
|
|
isShowUnreadCount = true;
|
|
@Input()
|
|
clearReadHere: boolean;
|
|
@Input()
|
|
minShowReadHere = 10;
|
|
@Input()
|
|
translationSimpleview = false;
|
|
@Input()
|
|
searchingMode = false;
|
|
|
|
@Output()
|
|
openProfile = new EventEmitter<number>();
|
|
@Output()
|
|
moreEvent = new EventEmitter<number>();
|
|
@Output()
|
|
massDetail = new EventEmitter<number>();
|
|
@Output()
|
|
massTranslationDetail = new EventEmitter<{
|
|
message: Info<MassTranslationEventJson>;
|
|
contentsType: string;
|
|
}>();
|
|
@Output()
|
|
fileViewer = new EventEmitter<SelectFileInfo>();
|
|
@Output()
|
|
save = new EventEmitter<{
|
|
fileInfo: FileEventJson;
|
|
fileDownloadItem: FileDownloadItem;
|
|
type: string;
|
|
}>();
|
|
@Output()
|
|
contextMenu = new EventEmitter<{
|
|
event: MouseEvent;
|
|
message: Info<EventJson>;
|
|
type?: string;
|
|
}>();
|
|
|
|
@Output()
|
|
scrollUp = new EventEmitter<any>();
|
|
|
|
@Output()
|
|
yReachEnd = new EventEmitter<any>();
|
|
@Output()
|
|
yReachStart = new EventEmitter<any>();
|
|
|
|
@Output()
|
|
existNewMessage = new EventEmitter<Info<EventJson>>();
|
|
|
|
@ViewChild('chatMessagesContainer', { static: false })
|
|
chatMessagesContainer: ElementRef<HTMLElement>;
|
|
|
|
@ViewChild('chatMessagesBuffer', { static: false })
|
|
chatMessagesBuffer: ElementRef<HTMLElement>;
|
|
|
|
@ViewChild('chatMessagesBufferContainer', { static: false })
|
|
chatMessagesBufferContainer: ElementRef<HTMLElement>;
|
|
|
|
@ViewChild(VirtualScrollerComponent, { static: false })
|
|
private virtualScroller: VirtualScrollerComponent;
|
|
|
|
@ViewChildren('chatMessageBox')
|
|
chatMessageBoxList: QueryList<MessageBoxComponent>;
|
|
|
|
storedScrollItem: Info<EventJson>; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩.
|
|
storedScrollItemOffsetTop: number | undefined;
|
|
scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다.
|
|
firstCheckReadHere = true;
|
|
initRoomLastEventSeq: number;
|
|
baseEventSeq = 0;
|
|
gotoEventSeq: number;
|
|
|
|
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;
|
|
|
|
readToHereEvent: Info<EventJson>;
|
|
existReadToHereEvent = true;
|
|
hidden = false;
|
|
swapped = false;
|
|
initalized = false;
|
|
|
|
constructor(
|
|
private logger: NGXLogger,
|
|
private changeDetectorRef: ChangeDetectorRef,
|
|
private translateService: TranslateService
|
|
) {}
|
|
|
|
ngOnInit() {
|
|
this.profileImageRoot =
|
|
this.profileImageRoot || this.sessionVerInfo.profileRoot;
|
|
|
|
this.loginResSubscription = this.loginRes$.subscribe(loginRes => {
|
|
this.loginRes = loginRes;
|
|
});
|
|
this.roomInfoSubscription = this.roomInfo$.subscribe(roomInfo => {
|
|
this.roomInfo = roomInfo;
|
|
this.initalized = false;
|
|
|
|
/** [S] initializing by changed room */
|
|
// reset :: roomLastEventSeq
|
|
if (!!roomInfo && !!roomInfo.finalEventSeq) {
|
|
this.initRoomLastEventSeq = roomInfo.finalEventSeq;
|
|
}
|
|
// clear :: readToHearEvent object
|
|
this.readToHereEvent = undefined;
|
|
this.existReadToHereEvent = true;
|
|
/** [E] initializing by changed room */
|
|
|
|
if (!this.roomInfo || this.roomInfo.roomSeq !== roomInfo.roomSeq) {
|
|
this.initEventMore();
|
|
}
|
|
});
|
|
this.eventListSubscription = this.eventList$
|
|
.pipe(debounce(() => timer(100)))
|
|
.subscribe(eventList => {
|
|
this.eventList = eventList;
|
|
|
|
if (!!eventList && eventList.length > 0) {
|
|
if (!this.readToHereEvent && this.existReadToHereEvent) {
|
|
this.readToHereEvent = this.getReadHere();
|
|
}
|
|
|
|
if (
|
|
this.baseEventSeq > 0 &&
|
|
!!this.roomInfo &&
|
|
!!this.roomInfo.lastReadEventSeq &&
|
|
this.baseEventSeq <= this.roomInfo.lastReadEventSeq
|
|
) {
|
|
// 기존 대화 내용이 있는 상태에서 추가로 조회된 내용중에 read here 가 있을 경우.
|
|
this.firstCheckReadHere = false;
|
|
}
|
|
} else {
|
|
this.readToHereEvent = undefined;
|
|
}
|
|
|
|
this.changeDetectorRef.detectChanges();
|
|
|
|
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.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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* UserInfo getter
|
|
*/
|
|
getUserName(seq: number): string {
|
|
if (!this.userInfos) {
|
|
return '';
|
|
}
|
|
|
|
const userInfo: UserInfo[] = this.userInfos.filter(
|
|
user => user.seq === seq
|
|
);
|
|
if (!!userInfo && userInfo.length > 0) {
|
|
return userInfo[0].name;
|
|
}
|
|
return '(알수없는 사용자)';
|
|
}
|
|
getUserProfile(seq: number): string {
|
|
if (!this.userInfos) {
|
|
return '';
|
|
}
|
|
|
|
const userInfo: UserInfo[] = this.userInfos.filter(
|
|
user => user.seq === seq
|
|
);
|
|
if (!!userInfo && userInfo.length > 0) {
|
|
return userInfo[0].profileImageFile;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
isHighlightedEvent(seq: number): boolean {
|
|
return (
|
|
!!this.searchedList &&
|
|
this.searchedList.filter(event => event.seq === seq).length > 0
|
|
);
|
|
}
|
|
|
|
getUnreadCount(message: Info<EventJson>): string | number {
|
|
// if (!this.userInfos || 0 === this.userInfos.length) {
|
|
// return '';
|
|
// }
|
|
const unreadCnt = this.userInfos
|
|
.filter(user => user.isJoinRoom && user.seq !== message.senderSeq)
|
|
.filter(user => user.lastReadEventSeq < message.seq).length;
|
|
return unreadCnt === 0 ? '' : unreadCnt;
|
|
}
|
|
|
|
/**
|
|
* 정보성 Event 인지 판단.
|
|
* @description 정보성 event 일 경우 프로필, 일시 를 표현하지 않는다.
|
|
* Edit with reducers.ts / sync / updateRoomForNewEventMessage
|
|
*/
|
|
getIsInformation(info: Info<EventJson>) {
|
|
if (
|
|
info.type === EventType.Join ||
|
|
info.type === EventType.Exit ||
|
|
info.type === EventType.ForcedExit ||
|
|
info.type === EventType.RenameRoom ||
|
|
info.type === EventType.NotificationForTimerRoom ||
|
|
info.type === EventType.GuideForRoomTimerChanged
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Date Splitter show check */
|
|
getDateSplitter(message: Info<EventJson>): boolean {
|
|
const curIndex = this.eventList.findIndex(v => v.seq === message.seq);
|
|
|
|
if (curIndex === 0) {
|
|
return true;
|
|
}
|
|
if (curIndex > 0) {
|
|
if (!this.eventList[curIndex]) {
|
|
return false;
|
|
}
|
|
return !moment(this.eventList[curIndex].sendDate).isSame(
|
|
moment(this.eventList[curIndex - 1].sendDate),
|
|
'day'
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
getReadHere(): Info<EventJson> | undefined {
|
|
if (
|
|
!!this.roomInfo &&
|
|
!!this.roomInfo.lastReadEventSeq &&
|
|
this.initRoomLastEventSeq - this.roomInfo.lastReadEventSeq >
|
|
this.minShowReadHere
|
|
) {
|
|
if (
|
|
this.roomInfo.roomType === RoomType.Single ||
|
|
this.roomInfo.roomType === RoomType.Multi
|
|
) {
|
|
if (!this.roomInfo.isTimeRoom) {
|
|
return this.eventList.find(
|
|
v => v.seq === this.roomInfo.lastReadEventSeq + 1
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
this.existReadToHereEvent = false;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
getStringReadHereMore(): string {
|
|
let rtnStr = '';
|
|
rtnStr = this.translateService.instant('chat.event.moreUnreadEventsWith', {
|
|
countOfUnread: this.baseEventSeq - (this.roomInfo.lastReadEventSeq + 1)
|
|
});
|
|
return rtnStr;
|
|
}
|
|
|
|
storeScrollPosition() {
|
|
this.storedScrollItem = this.eventList[
|
|
this.virtualScroller.viewPortInfo.startIndex
|
|
];
|
|
|
|
const chatMessageBox = this.chatMessageBoxList.find(
|
|
el =>
|
|
el.message.seq ===
|
|
this.eventList[this.virtualScroller.viewPortInfo.startIndex].seq
|
|
);
|
|
if (!!chatMessageBox) {
|
|
this.storedScrollItemOffsetTop =
|
|
chatMessageBox.offsetTop -
|
|
this.virtualScroller.viewPortInfo.scrollStartPosition;
|
|
} else {
|
|
this.storedScrollItemOffsetTop = 0;
|
|
}
|
|
}
|
|
|
|
swapScrollTo(
|
|
to: Info<EventJson>,
|
|
preCallback: () => void,
|
|
postCallback: () => void,
|
|
useHide: boolean,
|
|
useSwap: boolean,
|
|
addtionalOffset?: number
|
|
) {
|
|
this.preSwapScroll(useHide, useSwap);
|
|
if (!!preCallback) {
|
|
preCallback();
|
|
}
|
|
|
|
this.virtualScroller.scrollInto(
|
|
to,
|
|
true,
|
|
undefined !== this.storedScrollItemOffsetTop
|
|
? -this.storedScrollItemOffsetTop
|
|
: undefined !== addtionalOffset
|
|
? -addtionalOffset
|
|
: 0,
|
|
0,
|
|
() => {
|
|
setTimeout(() => {
|
|
if (!!postCallback) {
|
|
postCallback();
|
|
}
|
|
this.postSwapScroll(useHide, useSwap);
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
preSwapScroll(useHide: boolean, useSwap: boolean) {
|
|
if (useSwap && !this.swapped) {
|
|
this.chatMessagesBuffer.nativeElement.innerHTML = this.chatMessagesContainer.nativeElement.innerHTML;
|
|
this.chatMessagesBuffer.nativeElement.scrollTop = this.chatMessagesContainer.nativeElement.scrollTop;
|
|
this.chatMessagesBufferContainer.nativeElement.classList.remove(
|
|
'disappear'
|
|
);
|
|
this.swapped = true;
|
|
}
|
|
|
|
if (useHide && !this.hidden) {
|
|
this.chatMessagesContainer.nativeElement.classList.add('hide');
|
|
this.hidden = true;
|
|
}
|
|
}
|
|
|
|
postSwapScroll(useHide: boolean, useSwap: boolean) {
|
|
if (useSwap && this.swapped) {
|
|
this.chatMessagesBuffer.nativeElement.innerHTML = '';
|
|
this.chatMessagesBuffer.nativeElement.scrollTop = 0;
|
|
this.chatMessagesBufferContainer.nativeElement.classList.add('disappear');
|
|
this.swapped = false;
|
|
}
|
|
|
|
if (useHide && this.hidden) {
|
|
this.chatMessagesContainer.nativeElement.classList.remove('hide');
|
|
this.hidden = false;
|
|
}
|
|
}
|
|
|
|
ready(): void {
|
|
this.scrollToBottom();
|
|
}
|
|
|
|
scrollToBottom(): void {
|
|
if (!!this.storedScrollItem) {
|
|
if (!!this.readToHereEvent && this.firstCheckReadHere) {
|
|
this.swapScrollTo(
|
|
this.readToHereEvent,
|
|
() => {},
|
|
() => {
|
|
this.firstCheckReadHere = false;
|
|
},
|
|
true,
|
|
true
|
|
);
|
|
} else {
|
|
this.swapScrollTo(
|
|
this.storedScrollItem,
|
|
() => {},
|
|
() => {
|
|
this.storedScrollItem = undefined;
|
|
this.storedScrollItemOffsetTop = undefined;
|
|
},
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
} else if (this.scrollUpInitalized) {
|
|
if (!!this.newEventList && this.newEventList.length > 0) {
|
|
this.existNewMessage.emit(
|
|
this.newEventList[this.newEventList.length - 1]
|
|
);
|
|
}
|
|
} else {
|
|
if (!!this.readToHereEvent && this.firstCheckReadHere) {
|
|
this.swapScrollTo(
|
|
this.readToHereEvent,
|
|
() => {},
|
|
() => {
|
|
this.firstCheckReadHere = false;
|
|
},
|
|
false,
|
|
false
|
|
);
|
|
} else {
|
|
if (!this.eventList || 0 === this.eventList.length) {
|
|
return;
|
|
}
|
|
|
|
if (!!this.gotoEventSeq) {
|
|
this.gotoPosition(this.gotoEventSeq);
|
|
this.gotoEventSeq = undefined;
|
|
return;
|
|
}
|
|
|
|
const lastEvent =
|
|
!!this.eventList && 0 < this.eventList.length
|
|
? this.eventList[this.eventList.length - 1]
|
|
: undefined;
|
|
|
|
if (undefined === lastEvent) {
|
|
return;
|
|
}
|
|
const isInViewPortItems = this.isInViewPortItems(lastEvent.seq);
|
|
this.swapScrollTo(
|
|
this.eventList[this.eventList.length - 1],
|
|
() => {},
|
|
() => {
|
|
this.initalized = true;
|
|
},
|
|
-1 === this.virtualScroller.viewPortInfo.endIndex ||
|
|
!isInViewPortItems,
|
|
!isInViewPortItems
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
initEventMore(gotoEventSeq?: number) {
|
|
// 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다.
|
|
this.scrollUpInitalized = false;
|
|
this.storedScrollItem = undefined;
|
|
this.storedScrollItemOffsetTop = undefined;
|
|
this.gotoEventSeq = gotoEventSeq;
|
|
}
|
|
|
|
clear() {}
|
|
|
|
gotoPosition(eventSeq: number) {
|
|
const isInViewPortItems = this.isInViewPortItems(eventSeq);
|
|
|
|
if (!!this.virtualScroller) {
|
|
const e = this.eventList.find(v => v.seq === eventSeq);
|
|
this.swapScrollTo(
|
|
e,
|
|
() => {},
|
|
() => {
|
|
const chatMessageBox = this.chatMessageBoxList.find(
|
|
el => el.message.seq === eventSeq
|
|
);
|
|
if (!!chatMessageBox) {
|
|
chatMessageBox.shake();
|
|
}
|
|
},
|
|
!isInViewPortItems,
|
|
!isInViewPortItems,
|
|
50
|
|
);
|
|
}
|
|
}
|
|
|
|
isInViewPortItems(eventSeq: number): boolean {
|
|
if (undefined === eventSeq) {
|
|
return false;
|
|
}
|
|
const viewPortItemIndex = this.virtualScroller.viewPortItems.findIndex(
|
|
v => v.seq === eventSeq
|
|
);
|
|
|
|
const newEvent =
|
|
!!this.newEventList &&
|
|
-1 !== this.newEventList.findIndex(e => e.seq === eventSeq);
|
|
|
|
return -1 !== viewPortItemIndex || newEvent;
|
|
}
|
|
|
|
onClickOpenProfile(userSeq: number) {
|
|
this.openProfile.emit(userSeq);
|
|
}
|
|
|
|
onClickMore(event: any) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
if (this.scrollUpInitalized && this.eventRemained) {
|
|
this.virtualScroller.scrollToPosition(0);
|
|
this.virtualScroller.invalidateCachedMeasurementAtIndex(0);
|
|
|
|
this.storeScrollPosition();
|
|
|
|
this.preSwapScroll(true, true);
|
|
|
|
this.moreEvent.emit(this.eventList[0].seq);
|
|
}
|
|
}
|
|
|
|
/** [Event] MassTalk Detail View */
|
|
onMassDetail(value: number) {
|
|
this.massDetail.emit(value);
|
|
}
|
|
|
|
onMassTranslationDetail(params: {
|
|
message: Info<MassTranslationEventJson>;
|
|
contentsType: string;
|
|
}) {
|
|
this.massTranslationDetail.emit(params);
|
|
}
|
|
|
|
/** [Event] Image Viewer */
|
|
onFileViewer(fileInfo: SelectFileInfo) {
|
|
this.fileViewer.emit(fileInfo);
|
|
}
|
|
|
|
/** [Event] Attach File Save & Save As */
|
|
onSave(value: {
|
|
fileInfo: FileEventJson;
|
|
fileDownloadItem: FileDownloadItem;
|
|
type: string;
|
|
}) {
|
|
this.save.emit(value);
|
|
}
|
|
|
|
/** [Event] Context Menu */
|
|
onContextMenu(event: {
|
|
event: MouseEvent;
|
|
message: Info<EventJson>;
|
|
type?: string;
|
|
}) {
|
|
this.contextMenu.emit(event);
|
|
}
|
|
|
|
onScrollup(event: any) {
|
|
if (!this.eventList || 0 === this.eventList.length) {
|
|
return;
|
|
}
|
|
this.scrollUpInitalized = true;
|
|
this.scrollUp.emit(event);
|
|
}
|
|
onYReachStart(event: any) {
|
|
this.yReachStart.emit(event);
|
|
}
|
|
onYReachEnd(event: any) {
|
|
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 {
|
|
return info.seq;
|
|
}
|
|
|
|
compareItemsFunc = (
|
|
item1: Info<EventJson>,
|
|
item2: Info<EventJson>
|
|
// tslint:disable-next-line: semicolon
|
|
): boolean => !!item1 && !!item2 && item1.seq === item2.seq;
|
|
}
|