unread count 관련 처리.
This commit is contained in:
parent
d099bcb035
commit
33e6566eff
|
@ -137,7 +137,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewChecked {
|
||||||
|
|
||||||
this.userInfoListSubscription = this.store
|
this.userInfoListSubscription = this.store
|
||||||
.pipe(
|
.pipe(
|
||||||
select(AppStore.MessengerSelector.RoomSelector.userInfoList),
|
select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist),
|
||||||
tap(userInfoList => {
|
tap(userInfoList => {
|
||||||
this.userInfoList = userInfoList;
|
this.userInfoList = userInfoList;
|
||||||
})
|
})
|
||||||
|
|
|
@ -154,11 +154,7 @@ export class AppNotificationService {
|
||||||
'Notification::eventProtocolService::ReadNotification',
|
'Notification::eventProtocolService::ReadNotification',
|
||||||
noti
|
noti
|
||||||
);
|
);
|
||||||
this.store.dispatch(
|
this.store.dispatch(EventStore.readNotification(noti));
|
||||||
EventStore.readNotification({
|
|
||||||
noti
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SSVC_TYPE_EVENT_CANCEL_NOTI:
|
case SSVC_TYPE_EVENT_CANCEL_NOTI:
|
||||||
|
|
|
@ -133,7 +133,7 @@ export const readFailure = createAction(
|
||||||
|
|
||||||
export const readNotification = createAction(
|
export const readNotification = createAction(
|
||||||
'[Messenger::Event] Read Notification',
|
'[Messenger::Event] Read Notification',
|
||||||
props<{ noti: ReadNotification }>()
|
props<ReadNotification>()
|
||||||
);
|
);
|
||||||
|
|
||||||
/** 대화 회수 */
|
/** 대화 회수 */
|
||||||
|
|
|
@ -32,10 +32,14 @@ import {
|
||||||
ReadResponse,
|
ReadResponse,
|
||||||
DelResponse,
|
DelResponse,
|
||||||
CancelResponse,
|
CancelResponse,
|
||||||
EventType
|
EventType,
|
||||||
|
ReadNotification,
|
||||||
|
SSVC_TYPE_EVENT_SEND_RES,
|
||||||
|
SSVC_TYPE_EVENT_SEND_NOTI
|
||||||
} from '@ucap-webmessenger/protocol-event';
|
} from '@ucap-webmessenger/protocol-event';
|
||||||
|
|
||||||
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 SyncStore from '@app/store/messenger/sync';
|
import * as SyncStore from '@app/store/messenger/sync';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -149,7 +153,7 @@ export class Effects {
|
||||||
exhaustMap(req =>
|
exhaustMap(req =>
|
||||||
this.eventProtocolService.read(req).pipe(
|
this.eventProtocolService.read(req).pipe(
|
||||||
map((res: ReadResponse) => {
|
map((res: ReadResponse) => {
|
||||||
return readNotification({ noti: res });
|
return readNotification(res as ReadNotification);
|
||||||
}),
|
}),
|
||||||
catchError(error => of(readFailure({ error })))
|
catchError(error => of(readFailure({ error })))
|
||||||
)
|
)
|
||||||
|
@ -161,14 +165,33 @@ export class Effects {
|
||||||
() => {
|
() => {
|
||||||
return this.actions$.pipe(
|
return this.actions$.pipe(
|
||||||
ofType(readNotification),
|
ofType(readNotification),
|
||||||
map(action => action.noti),
|
withLatestFrom(
|
||||||
tap(noti => {
|
this.store.pipe(
|
||||||
this.store.dispatch(
|
select((state: any) => state.messenger.room.roomInfo as RoomInfo)
|
||||||
SyncStore.updateUnreadCount({
|
),
|
||||||
roomSeq: noti.roomSeq,
|
this.store.pipe(
|
||||||
noReadCnt: 0
|
select(
|
||||||
})
|
(state: any) =>
|
||||||
);
|
state.account.authentication.loginRes as LoginResponse
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// map(([action, roomInfo]) => ([action.noti, roomInfo])),
|
||||||
|
tap(([action, roomInfo, loginRes]) => {
|
||||||
|
// 현재 오픈된 방에 대한 read noti/res 가 유입될 경우 roomUserList 의 lastReadSeq 를 갱신한다.
|
||||||
|
if (roomInfo.roomSeq === action.roomSeq) {
|
||||||
|
this.store.dispatch(RoomStore.updateRoomUserLastReadSeq(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
// noti/res 를 일으킨 주체가 본인이라면 현재 오픈된 방 여부에 상관없이 해당방에 대한 noReadCnt 를 초기화 한다.
|
||||||
|
if (action.SENDER_SEQ === loginRes.userSeq) {
|
||||||
|
this.store.dispatch(
|
||||||
|
SyncStore.updateUnreadCount({
|
||||||
|
roomSeq: action.roomSeq,
|
||||||
|
noReadCnt: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -376,12 +399,24 @@ export class Effects {
|
||||||
// opened room :: event add
|
// opened room :: event add
|
||||||
if (!!roomInfo && roomInfo.roomSeq === action.roomSeq) {
|
if (!!roomInfo && roomInfo.roomSeq === action.roomSeq) {
|
||||||
this.store.dispatch(appendInfoList({ info: action.info }));
|
this.store.dispatch(appendInfoList({ info: action.info }));
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.SVC_TYPE === SVC_TYPE_EVENT &&
|
||||||
|
action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI
|
||||||
|
) {
|
||||||
|
this.store.dispatch(
|
||||||
|
read({
|
||||||
|
roomSeq: action.roomSeq,
|
||||||
|
lastReadSeq: action.info.seq
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not opened room :: unread count increased
|
// not opened room :: unread count increased
|
||||||
if (
|
if (
|
||||||
action.SVC_TYPE === SVC_TYPE_EVENT &&
|
action.SVC_TYPE === SVC_TYPE_EVENT &&
|
||||||
action.SSVC_TYPE === SSVC_TYPE_EVENT_INFO_RES
|
action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 다른 디바이스에서 대화를 송신 할경우 RES 가 noti 로 유입될 수 있다.
|
* 다른 디바이스에서 대화를 송신 할경우 RES 가 noti 로 유입될 수 있다.
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
ExitRequest,
|
ExitRequest,
|
||||||
ExitResponse
|
ExitResponse
|
||||||
} from '@ucap-webmessenger/protocol-room';
|
} from '@ucap-webmessenger/protocol-room';
|
||||||
|
import { ReadNotification } from '@ucap-webmessenger/protocol-event';
|
||||||
|
|
||||||
export const info = createAction(
|
export const info = createAction(
|
||||||
'[Messenger::Room] Info',
|
'[Messenger::Room] Info',
|
||||||
|
@ -60,6 +61,11 @@ export const updateOnlyAlarm = createAction(
|
||||||
props<{ roomInfo: RoomInfo }>()
|
props<{ roomInfo: RoomInfo }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const updateRoomUserLastReadSeq = createAction(
|
||||||
|
'[Messenger::Room] Update Room User Only LastReadSeq',
|
||||||
|
props<ReadNotification>()
|
||||||
|
);
|
||||||
|
|
||||||
export const update = createAction(
|
export const update = createAction(
|
||||||
'[Messenger::Room] Update',
|
'[Messenger::Room] Update',
|
||||||
props<{ req: UpdateRequest }>()
|
props<{ req: UpdateRequest }>()
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { createReducer, on } from '@ngrx/store';
|
import { createReducer, on } from '@ngrx/store';
|
||||||
import { initialState } from './state';
|
import { initialState, adapterUserInfo, adapterUserInfoShort } from './state';
|
||||||
import { infoSuccess, updateSuccess } from './actions';
|
import {
|
||||||
|
infoSuccess,
|
||||||
|
updateSuccess,
|
||||||
|
updateRoomUserLastReadSeq
|
||||||
|
} from './actions';
|
||||||
|
|
||||||
import * as AuthenticationStore from '@app/store/account/authentication';
|
import * as AuthenticationStore from '@app/store/account/authentication';
|
||||||
|
import { UserInfo } from '@ucap-webmessenger/protocol-room';
|
||||||
|
|
||||||
export const reducer = createReducer(
|
export const reducer = createReducer(
|
||||||
initialState,
|
initialState,
|
||||||
|
@ -10,8 +15,12 @@ export const reducer = createReducer(
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
roomInfo: action.roomInfo,
|
roomInfo: action.roomInfo,
|
||||||
userInfoList: action.userInfoList,
|
userInfoList: adapterUserInfo.addAll(action.userInfoList, {
|
||||||
userInfoShortList: action.userInfoShortList
|
...state.userInfoList
|
||||||
|
}),
|
||||||
|
userInfoShortList: adapterUserInfoShort.addAll(action.userInfoShortList, {
|
||||||
|
...state.userInfoShortList
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -26,6 +35,23 @@ export const reducer = createReducer(
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
on(updateRoomUserLastReadSeq, (state, action) => {
|
||||||
|
const userSeq = action.SENDER_SEQ;
|
||||||
|
|
||||||
|
const userInfo: UserInfo = {
|
||||||
|
...state.userInfoList.entities[userSeq],
|
||||||
|
lastReadEventSeq: action.lastReadSeq
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userInfoList: adapterUserInfo.updateOne(
|
||||||
|
{ id: userSeq, changes: userInfo },
|
||||||
|
{ ...state.userInfoList }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
on(AuthenticationStore.logout, (state, action) => {
|
on(AuthenticationStore.logout, (state, action) => {
|
||||||
return {
|
return {
|
||||||
...initialState
|
...initialState
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { EntityState, createEntityAdapter } from '@ngrx/entity';
|
||||||
import { Selector, createSelector } from '@ngrx/store';
|
import { Selector, createSelector } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
InfoRequest,
|
InfoRequest,
|
||||||
|
@ -6,31 +7,71 @@ import {
|
||||||
UserInfo
|
UserInfo
|
||||||
} from '@ucap-webmessenger/protocol-room';
|
} from '@ucap-webmessenger/protocol-room';
|
||||||
|
|
||||||
|
export interface UserInfoShortListState extends EntityState<UserInfoShort> {}
|
||||||
|
export interface UserInfoListState extends EntityState<UserInfo> {}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
roomInfo: RoomInfo | null;
|
roomInfo: RoomInfo | null;
|
||||||
userInfoShortList: UserInfoShort[] | null;
|
userInfoShortList: UserInfoShortListState;
|
||||||
userInfoList: UserInfo[] | null;
|
userInfoList: UserInfoListState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const adapterUserInfoShort = createEntityAdapter<UserInfoShort>({
|
||||||
|
selectId: userInfo => userInfo.seq
|
||||||
|
});
|
||||||
|
export const adapterUserInfo = createEntityAdapter<UserInfo>({
|
||||||
|
selectId: userInfo => userInfo.seq
|
||||||
|
});
|
||||||
|
|
||||||
|
const userInfoShortListInitialState: UserInfoShortListState = adapterUserInfoShort.getInitialState(
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const userInfoListInitialState: UserInfoListState = adapterUserInfo.getInitialState(
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
export const initialState: State = {
|
export const initialState: State = {
|
||||||
roomInfo: null,
|
roomInfo: null,
|
||||||
userInfoShortList: null,
|
userInfoShortList: userInfoShortListInitialState,
|
||||||
userInfoList: null
|
userInfoList: userInfoListInitialState
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectAll: ngeSelectAllUserInfoShort,
|
||||||
|
selectEntities: ngeSelectEntitiesUserInfoShort,
|
||||||
|
selectIds: ngeSelectIdsUserInfoShort,
|
||||||
|
selectTotal: ngeSelectTotalUserInfoShort
|
||||||
|
} = adapterUserInfoShort.getSelectors();
|
||||||
|
const {
|
||||||
|
selectAll: ngeSelectAllUserInfo,
|
||||||
|
selectEntities: ngeSelectEntitiesUserInfo,
|
||||||
|
selectIds: ngeSelectIdsUserInfo,
|
||||||
|
selectTotal: ngeSelectTotalUserInfo
|
||||||
|
} = adapterUserInfo.getSelectors();
|
||||||
|
|
||||||
export function selectors<S>(selector: Selector<any, State>) {
|
export function selectors<S>(selector: Selector<any, State>) {
|
||||||
|
const selectUserInfoShortList = createSelector(
|
||||||
|
selector,
|
||||||
|
(state: State) => state.userInfoShortList
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectUserInfoList = createSelector(
|
||||||
|
selector,
|
||||||
|
(state: State) => state.userInfoList
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomInfo: createSelector(
|
roomInfo: createSelector(
|
||||||
selector,
|
selector,
|
||||||
(state: State) => state.roomInfo
|
(state: State) => state.roomInfo
|
||||||
),
|
),
|
||||||
userInfoShortList: createSelector(
|
selectUserInfoShortList: createSelector(
|
||||||
selector,
|
selectUserInfoShortList,
|
||||||
(state: State) => state.userInfoShortList
|
ngeSelectAllUserInfoShort
|
||||||
),
|
),
|
||||||
userInfoList: createSelector(
|
selectUserinfolist: createSelector(
|
||||||
selector,
|
selectUserInfoList,
|
||||||
(state: State) => state.userInfoList
|
ngeSelectAllUserInfo
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
} from '@ucap-webmessenger/protocol-sync';
|
} from '@ucap-webmessenger/protocol-sync';
|
||||||
import {
|
import {
|
||||||
RoomInfo,
|
RoomInfo,
|
||||||
UserInfoShort,
|
|
||||||
UserInfo as RoomUserInfo
|
UserInfo as RoomUserInfo
|
||||||
} from '@ucap-webmessenger/protocol-room';
|
} from '@ucap-webmessenger/protocol-room';
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="time secondary-text">
|
<div class="time secondary-text">
|
||||||
{{ message.sendDate | date: 'a hh:mm' }}
|
<ul>
|
||||||
|
<li>{{ getUnreadCount(message) }}</li>
|
||||||
|
<li>
|
||||||
|
{{ message.sendDate | date: 'a hh:mm' }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -140,23 +140,23 @@ $meBox-bg: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.information-msg{
|
.information-msg {
|
||||||
width:100%;
|
width: 100%;
|
||||||
height:100%;
|
height: 100%;
|
||||||
text-align:center;
|
text-align: center;
|
||||||
background-color:#cccccc;
|
background-color: #cccccc;
|
||||||
padding:10px;
|
padding: 10px;
|
||||||
margin:10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-row {
|
.message-row {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
.date-splitter{
|
.date-splitter {
|
||||||
display:block;
|
display: block;
|
||||||
width:100%;
|
width: 100%;
|
||||||
margin-bottom:10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.chat-row{
|
.chat-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -165,14 +165,14 @@ $meBox-bg: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.me {
|
&.me {
|
||||||
.chat-row{
|
.chat-row {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
.profile-info {
|
.profile-info {
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-main {
|
.message-main {
|
||||||
|
@ -219,10 +219,15 @@ $meBox-bg: #ffffff;
|
||||||
.secondary-text {
|
.secondary-text {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color:#666666;
|
color: #666666;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
&.me {
|
||||||
|
.secondary-text {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-row.me > .bubble {
|
.message-row.me > .bubble {
|
||||||
|
@ -267,4 +272,4 @@ $meBox-bg: #ffffff;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,8 @@ import {
|
||||||
EventType,
|
EventType,
|
||||||
InfoResponse
|
InfoResponse
|
||||||
} from '@ucap-webmessenger/protocol-event';
|
} from '@ucap-webmessenger/protocol-event';
|
||||||
import {
|
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
||||||
LoginResponse,
|
import { UserInfo } from '@ucap-webmessenger/protocol-room';
|
||||||
UserInfo
|
|
||||||
} from '@ucap-webmessenger/protocol-authentication';
|
|
||||||
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 { FileInfo } from '../models/file-info.json';
|
||||||
|
@ -61,6 +59,9 @@ export class MessagesComponent implements OnInit {
|
||||||
this.profileImageRoot || this.sessionVerInfo.profileRoot;
|
this.profileImageRoot || this.sessionVerInfo.profileRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserInfo getter
|
||||||
|
*/
|
||||||
getUserName(seq: number): string {
|
getUserName(seq: number): string {
|
||||||
if (!this.userInfos) {
|
if (!this.userInfos) {
|
||||||
return '';
|
return '';
|
||||||
|
@ -88,6 +89,17 @@ export class MessagesComponent implements OnInit {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUnreadCount(message: Info): string | number {
|
||||||
|
const unreadCnt = this.userInfos.filter(user => {
|
||||||
|
if (message.senderSeq === user.seq) {
|
||||||
|
// 본인 글은 unreadCount 에 포함하지 않는다.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return user.lastReadEventSeq < message.seq;
|
||||||
|
}).length;
|
||||||
|
return unreadCnt === 0 ? '' : unreadCnt;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 정보성 Event 인지 판단.
|
* 정보성 Event 인지 판단.
|
||||||
* @description 정보성 event 일 경우 프로필, 일시 를 표현하지 않는다.
|
* @description 정보성 event 일 경우 프로필, 일시 를 표현하지 않는다.
|
||||||
|
@ -106,7 +118,7 @@ export class MessagesComponent implements OnInit {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Date Splitter check */
|
/** Date Splitter show check */
|
||||||
getDateSplitter(curIndex: number): boolean {
|
getDateSplitter(curIndex: number): boolean {
|
||||||
if (curIndex === 0) {
|
if (curIndex === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -141,6 +153,7 @@ export class MessagesComponent implements OnInit {
|
||||||
this.save.emit(value);
|
this.save.emit(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** [Event] Context Menu */
|
||||||
onContextMenuMessage(event: MouseEvent, message: Info) {
|
onContextMenuMessage(event: MouseEvent, message: Info) {
|
||||||
this.contextMenu.emit({ event, message });
|
this.contextMenu.emit({ event, message });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user