대화방 > 이전 대화 보기 기능 구현. infinity scroll

This commit is contained in:
leejh 2019-10-31 18:12:38 +09:00
parent 4b4f8a0067
commit 749237b1bc
13 changed files with 131 additions and 27 deletions

View File

@ -19,7 +19,8 @@
{{ getRoomName() }} {{ getRoomName() }}
</div> </div>
<div *ngIf="!!roomInfo && roomInfo.isTimeRoom"> <div *ngIf="!!roomInfo && roomInfo.isTimeRoom">
<mat-icon>timer</mat-icon> {{ getConvertTimer(roomInfo.timeRoomInterval) }} <mat-icon>timer</mat-icon>
{{ getConvertTimer(roomInfo.timeRoomInterval) }}
</div> </div>
<div class="room-option"> <div class="room-option">
<button <button
@ -73,13 +74,19 @@
<!-- Timer Room Info --> <!-- Timer Room Info -->
<!-- CHAT MESSAGES --> <!-- CHAT MESSAGES -->
<perfect-scrollbar fxFlex="1 1 auto" #psChatContent> <perfect-scrollbar
fxFlex="1 1 auto"
#psChatContent
(psYReachStart)="onScrollup($event)"
>
<ucap-chat-messages <ucap-chat-messages
[messages]="eventList$ | async" [messages]="eventList$ | async"
[eventInfoStatus]="eventInfoStatus$ | async" [eventInfoStatus]="eventInfoStatus$ | async"
[eventRemain]="eventRemain$ | async"
[userInfos]="userInfoList" [userInfos]="userInfoList"
[loginRes]="loginRes" [loginRes]="loginRes"
[sessionVerInfo]="sessionVerInfo" [sessionVerInfo]="sessionVerInfo"
(moreEvent)="onMoreEvent($event)"
(massDetail)="onMassDetail($event)" (massDetail)="onMassDetail($event)"
(save)="onSave($event)" (save)="onSave($event)"
(imageViewer)="onImageViewer($event)" (imageViewer)="onImageViewer($event)"

View File

@ -58,7 +58,7 @@ import {
ImageViewerDialogData, ImageViewerDialogData,
ImageViewerDialogResult ImageViewerDialogResult
} from '@app/layouts/common/dialogs/image-viewer.dialog.component'; } from '@app/layouts/common/dialogs/image-viewer.dialog.component';
import { Maximum_Range } from '@ucap-webmessenger/core'; import { CONST } from '@ucap-webmessenger/core';
import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar'; import { PerfectScrollbarComponent } from 'ngx-perfect-scrollbar';
@Component({ @Component({
@ -83,12 +83,15 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
loginRes: LoginResponse; loginRes: LoginResponse;
loginResSubscription: Subscription; loginResSubscription: Subscription;
eventList$: Observable<Info[]>; eventList$: Observable<Info[]>;
baseEventSeq = 0;
roomInfo: RoomInfo; roomInfo: RoomInfo;
roomInfoSubscription: Subscription; roomInfoSubscription: Subscription;
userInfoList: UserInfo[]; userInfoList: UserInfo[];
userInfoListSubscription: Subscription; userInfoListSubscription: Subscription;
eventListProcessing$: Observable<boolean>; eventListProcessing$: Observable<boolean>;
eventInfoStatus$: Observable<InfoResponse>; eventInfoStatus$: Observable<InfoResponse>;
eventRemain$: Observable<boolean>;
eventRemain = false;
sessionVerInfo: VersionInfo2Response; sessionVerInfo: VersionInfo2Response;
isRecalledMessage = isRecalled; isRecalledMessage = isRecalled;
@ -148,8 +151,20 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
select(AppStore.MessengerSelector.EventSelector.infoListProcessing) select(AppStore.MessengerSelector.EventSelector.infoListProcessing)
); );
this.eventRemain$ = this.store.pipe(
select(AppStore.MessengerSelector.EventSelector.remainInfo),
tap(remainInfo => {
this.eventRemain = remainInfo;
})
);
this.eventList$ = this.store.pipe( this.eventList$ = this.store.pipe(
select(AppStore.MessengerSelector.EventSelector.selectAllInfoList) select(AppStore.MessengerSelector.EventSelector.selectAllInfoList),
tap(infoList => {
if (!!infoList && infoList.length > 0) {
this.baseEventSeq = infoList[0].seq;
}
})
); );
this.eventInfoStatus$ = this.store.pipe( this.eventInfoStatus$ = this.store.pipe(
@ -229,7 +244,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
return; return;
} }
if (message.trim().length > Maximum_Range.MassText) { if (message.trim().length > CONST.MASSTEXT_LEN) {
// MASS TEXT // MASS TEXT
this.store.dispatch( this.store.dispatch(
EventStore.sendMass({ EventStore.sendMass({
@ -260,6 +275,22 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
this.store.dispatch(RoomStore.updateOnlyAlarm({ roomInfo: this.roomInfo })); this.store.dispatch(RoomStore.updateOnlyAlarm({ roomInfo: this.roomInfo }));
} }
onScrollup(event: any) {
this.onMoreEvent(this.baseEventSeq);
}
/** More Event */
onMoreEvent(seq: number) {
if (this.eventRemain) {
this.store.dispatch(
EventStore.info({
roomSeq: this.roomInfo.roomSeq,
baseSeq: seq,
requestCount: CONST.EVENT_INFO_READ_COUNT
})
);
}
}
/** MassText Detail View */ /** MassText Detail View */
onMassDetail(value: number) { onMassDetail(value: number) {
this.store.dispatch( this.store.dispatch(

View File

@ -29,6 +29,14 @@ export const infoSuccess = createAction(
}>() }>()
); );
export const infoMoreSuccess = createAction(
'[Messenger::Event] Info More Success',
props<{
infoList: Info[];
res: InfoResponse;
}>()
);
export const infoFailure = createAction( export const infoFailure = createAction(
'[Messenger::Event] Info Failure', '[Messenger::Event] Info Failure',
props<{ error: any }>() props<{ error: any }>()

View File

@ -67,7 +67,8 @@ import {
forwardFailure, forwardFailure,
forwardAfterRoomOpen, forwardAfterRoomOpen,
sendMass, sendMass,
sendMassFailure sendMassFailure,
infoMoreSuccess
} from './actions'; } from './actions';
import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { import {
@ -81,6 +82,7 @@ import { openSuccess, openFailure } from '../room';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
import { StatusCode } from '@ucap-webmessenger/api'; import { StatusCode } from '@ucap-webmessenger/api';
import { CONST } from '@ucap-webmessenger/core';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -91,7 +93,7 @@ export class Effects {
return info({ return info({
roomSeq: action.roomSeq, roomSeq: action.roomSeq,
baseSeq: 0, baseSeq: 0,
requestCount: 50 requestCount: CONST.EVENT_INFO_READ_COUNT
}); });
}) })
) )
@ -115,12 +117,21 @@ export class Effects {
break; break;
case SSVC_TYPE_EVENT_INFO_RES: case SSVC_TYPE_EVENT_INFO_RES:
{ {
if (req.baseSeq === 0) {
this.store.dispatch( this.store.dispatch(
infoSuccess({ infoSuccess({
infoList, infoList,
res: res as InfoResponse res: res as InfoResponse
}) })
); );
} else {
this.store.dispatch(
infoMoreSuccess({
infoList,
res: res as InfoResponse
})
);
}
if (req.baseSeq === 0) { if (req.baseSeq === 0) {
// 최초 이벤트 목록 조회시 SSVC_TYPE_EVENT_READ_REQ 수행. // 최초 이벤트 목록 조회시 SSVC_TYPE_EVENT_READ_REQ 수행.

View File

@ -6,10 +6,12 @@ import {
info, info,
infoFailure, infoFailure,
recallInfoList, recallInfoList,
delInfoList delInfoList,
infoMoreSuccess
} from './actions'; } from './actions';
import * as AuthenticationStore from '@app/store/account/authentication'; import * as AuthenticationStore from '@app/store/account/authentication';
import { Info, EventType } from '@ucap-webmessenger/protocol-event'; import { Info, EventType } from '@ucap-webmessenger/protocol-event';
import { CONST } from '@ucap-webmessenger/core';
export const reducer = createReducer( export const reducer = createReducer(
initialState, initialState,
@ -27,7 +29,27 @@ export const reducer = createReducer(
...state.infoList ...state.infoList
}), }),
infoStatus: action.res, infoStatus: action.res,
infoListProcessing: false infoListProcessing: false,
remainInfo:
!!action.infoList &&
action.infoList.length === CONST.EVENT_INFO_READ_COUNT
? true
: false
};
}),
on(infoMoreSuccess, (state, action) => {
return {
...state,
infoList: adapterInfoList.upsertMany(action.infoList, {
...state.infoList
}),
infoStatus: action.res,
infoListProcessing: false,
remainInfo:
!!action.infoList &&
action.infoList.length === CONST.EVENT_INFO_READ_COUNT
? true
: false
}; };
}), }),

View File

@ -8,6 +8,7 @@ export interface State {
infoListProcessing: boolean; infoListProcessing: boolean;
infoList: InfoListState; infoList: InfoListState;
infoStatus: InfoResponse | null; infoStatus: InfoResponse | null;
remainInfo: boolean;
} }
export const adapterInfoList = createEntityAdapter<Info>({ export const adapterInfoList = createEntityAdapter<Info>({
@ -22,7 +23,8 @@ const infoListInitialState: InfoListState = adapterInfoList.getInitialState({});
export const initialState: State = { export const initialState: State = {
infoListProcessing: false, infoListProcessing: false,
infoList: infoListInitialState, infoList: infoListInitialState,
infoStatus: null infoStatus: null,
remainInfo: false
}; };
const { const {
@ -43,6 +45,10 @@ export function selectors<S>(selector: Selector<any, State>) {
selector, selector,
(state: State) => state.infoListProcessing (state: State) => state.infoListProcessing
), ),
remainInfo: createSelector(
selector,
(state: State) => state.remainInfo
),
infoList: createSelector( infoList: createSelector(
selector, selector,
(state: State) => state.infoList (state: State) => state.infoList

View File

@ -101,6 +101,7 @@ import {
import * as ChatStore from '@app/store/messenger/chat'; import * as ChatStore from '@app/store/messenger/chat';
import * as RoomStore from '@app/store/messenger/room'; import * as RoomStore from '@app/store/messenger/room';
import { CONST } from '@ucap-webmessenger/core';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -405,7 +406,7 @@ export class Effects {
divCd: 'DivCodeT', divCd: 'DivCodeT',
roomName: '', roomName: '',
isTimerRoom: true, isTimerRoom: true,
timerRoomInterval: 24 * 60 * 60, // 24h default timerRoomInterval: CONST.DEFAULT_TIMER_ROOM_INTERVAL, // 24h default
userSeqs: userSeqList userSeqs: userSeqList
} }
}) })

View File

@ -0,0 +1,10 @@
export enum CONST {
/** 대용량 텍스트로 보내는 문자열의 길이 기준 */
MASSTEXT_LEN = 800,
/** 대화방의 이벤트를 조회하는 개수 */
EVENT_INFO_READ_COUNT = 50,
/** Timer Room 최초 오픈시 timer interval */
DEFAULT_TIMER_ROOM_INTERVAL = 24 * 60 * 60,
/** 한번에 채팅을 할 수 있는 인원수 제한 */
CHATROOM_USER = 300
}

View File

@ -1,4 +0,0 @@
export enum Maximum_Range {
MassText = 800,
ChatRoom = 300
}

View File

@ -7,12 +7,12 @@ export * from './lib/type/call-alarm.type';
export * from './lib/type/call-forward.type'; export * from './lib/type/call-forward.type';
export * from './lib/type/call-mode.type'; export * from './lib/type/call-mode.type';
export * from './lib/type/caller-type.type'; export * from './lib/type/caller-type.type';
export * from './lib/type/const.type';
export * from './lib/type/default-screen.type'; export * from './lib/type/default-screen.type';
export * from './lib/type/device-devision.type'; export * from './lib/type/device-devision.type';
export * from './lib/type/device-type.type'; export * from './lib/type/device-type.type';
export * from './lib/type/file-transfer-permissions.type'; export * from './lib/type/file-transfer-permissions.type';
export * from './lib/type/locale-code.type'; export * from './lib/type/locale-code.type';
export * from './lib/type/maximum-range.type';
export * from './lib/type/notification-method.type'; export * from './lib/type/notification-method.type';
export * from './lib/type/organization-chart-permissions.type'; export * from './lib/type/organization-chart-permissions.type';
export * from './lib/type/push-type.type'; export * from './lib/type/push-type.type';

View File

@ -35,8 +35,6 @@ export interface InfoResponse extends ProtocolResponse {
baseSeq: number; baseSeq: number;
// 유효한파일기준이벤트SEQ(n) // 유효한파일기준이벤트SEQ(n)
validFileBaseSeq: number; validFileBaseSeq: number;
// 이벤트정보 개수(n)
count: number;
} }
export const encodeInfo: ProtocolEncoder<InfoRequest> = (req: InfoRequest) => { export const encodeInfo: ProtocolEncoder<InfoRequest> = (req: InfoRequest) => {
@ -82,7 +80,6 @@ export const decodeInfo: ProtocolDecoder<InfoResponse> = (
return decodeProtocolMessage(message, { return decodeProtocolMessage(message, {
roomSeq: message.bodyList[0], roomSeq: message.bodyList[0],
baseSeq: message.bodyList[1], baseSeq: message.bodyList[1],
validFileBaseSeq: message.bodyList[2], validFileBaseSeq: message.bodyList[2]
count: message.bodyList[3]
} as InfoResponse); } as InfoResponse);
}; };

View File

@ -1,4 +1,7 @@
<div class="chat-messages"> <div class="chat-messages">
<!-- <div class="message-row" *ngIf="eventRemain">
<button mat-button (click)="onClickMore($event)">이전 대화 보기</button>
</div> -->
<!-- MESSAGE --> <!-- MESSAGE -->
<div <div
*ngFor="let message of messages; let i = index" *ngFor="let message of messages; let i = index"

View File

@ -1,3 +1,4 @@
import { CONST } from '@ucap-webmessenger/core';
import { import {
Component, Component,
OnInit, OnInit,
@ -32,17 +33,20 @@ export class MessagesComponent implements OnInit {
@Input() @Input()
eventInfoStatus?: InfoResponse; eventInfoStatus?: InfoResponse;
@Input() @Input()
eventRemain: boolean;
@Input()
userInfos?: UserInfo[]; userInfos?: UserInfo[];
@Input() @Input()
sessionVerInfo: VersionInfo2Response; sessionVerInfo: VersionInfo2Response;
@Output()
moreEvent = new EventEmitter<number>();
@Output() @Output()
massDetail = new EventEmitter<number>(); massDetail = new EventEmitter<number>();
@Output() @Output()
imageViewer = new EventEmitter<FileInfo>(); imageViewer = new EventEmitter<FileInfo>();
@Output() @Output()
save = new EventEmitter<{ fileInfo: FileInfo; type: string }>(); save = new EventEmitter<{ fileInfo: FileInfo; type: string }>();
@Output() @Output()
contextMenu = new EventEmitter<{ contextMenu = new EventEmitter<{
event: MouseEvent; event: MouseEvent;
@ -50,6 +54,7 @@ export class MessagesComponent implements OnInit {
}>(); }>();
EventType = EventType; EventType = EventType;
CONST = CONST;
profileImageRoot: string; profileImageRoot: string;
constructor(private logger: NGXLogger, private datePipe: DatePipe) {} constructor(private logger: NGXLogger, private datePipe: DatePipe) {}
@ -138,6 +143,13 @@ export class MessagesComponent implements OnInit {
return false; return false;
} }
onClickMore(event: any) {
event.preventDefault();
event.stopPropagation();
this.moreEvent.emit(this.messages[0].seq);
}
/** [Event] MassTalk Detail View */ /** [Event] MassTalk Detail View */
onMassDetail(value: number) { onMassDetail(value: number) {
this.massDetail.emit(value); this.massDetail.emit(value);