대화방 > 이전 대화 보기 기능 구현. infinity scroll
This commit is contained in:
parent
4b4f8a0067
commit
749237b1bc
@ -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)"
|
||||||
|
@ -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(
|
||||||
|
@ -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 }>()
|
||||||
|
@ -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 수행.
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
10
projects/ucap-webmessenger-core/src/lib/type/const.type.ts
Normal file
10
projects/ucap-webmessenger-core/src/lib/type/const.type.ts
Normal 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
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
export enum Maximum_Range {
|
|
||||||
MassText = 800,
|
|
||||||
ChatRoom = 300
|
|
||||||
}
|
|
@ -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';
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user