This commit is contained in:
Park Byung Eun 2020-06-05 18:45:18 +09:00
parent 0c121ef1ad
commit cc59886a40
26 changed files with 2229 additions and 1 deletions

View File

@ -28,4 +28,15 @@ if (!!buddies && buddies.length > 0) {
}); });
console.log(userSeqsForAdd); console.log(userSeqsForAdd);
} }
update-user-dialog
manage-dialog
index.page
sidenav.page
updateMember
copy
move
add
create

View File

@ -0,0 +1,107 @@
프로필
나와의 채팅 진행 (O)
이벤트
파라미터(loginres, userseq )
채팅
대화 이벤트
답장
대화전달
대화복사
대화 나에게 전달
액션
대화 전달
타겟 대화방 번호
타겟 대화상대 번호
roomSeq = -999
eventType = c
sentMassage
대화방 오픈
대화방 오픈 프로토콜 호출
divCd: 'forwardOpen'
오픈성공
파일 타입
텍스트 타입
이벤트 전송
대화방 선택 처리
대화방 오픈 결과
대화 전송
액션 정의
forward => file type, bundle type, text type
openAfterSend => file type, text type
openAfter => event send
roomSelected =>
이펙트
chat foward
openAfterSend
selectedRoom
selectedRoomValidate
room2 success
events
events success
read
fileInfos
read success
fileInfos success
리듀서 구현
팝업 조직도
// forwardAfterRoomOpen$ = createEffect(() => {
// return this.actions$.pipe(
// ofType(forwardAfterRoomOpen),
// map((action) =>
// RoomActions.create({
// req: { divCd: 'forwardOpen', userSeqs: action.trgtUserSeqs }
// })
// ),
// exhaustMap((action) => {
// return [];
// })
// );
// });
/** forward */
export const forward = createAction(
'[ucap::chat::chatting] Forward',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);
/** chat forward failure */
export const forwardFailure = createAction(
'[ucap::chat::chatting] Forward failure',
props<{ error: any }>()
);
export const forwardAfterRoomOpen = createAction(
'[ucap::chat::chatting] Forward after room open',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);
export const roomOpenAfterForward = createAction(
'[ucap::chat::chatting] Room open after forward',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);

View File

@ -0,0 +1,26 @@
tap component
teanant search
bySearch, searchword
search result
bySearch
expansion
room list
ucap-chat-room-list-item-01
selection user
기존 절차
메세지 전송
대화방 대화 추가
대화방 갱신
대화방 마지막 대화 갱신
roomOpenAfterForward
newInfo
appedInfoList
newEventMessage
updateRoomForNewEventMessage
selectedRoom
clearEvent
info

View File

@ -0,0 +1,70 @@
대화 복사
일반 텍스트
대용량 텍스트
일반 번역 텍스트
대용량 번역 텍스트
ucap prj
protocol-event isRecall fnc 데이터형 number로 변경
daesang recall effect
cancel (roomseq, eventseq, deviceType)
cnacelNotification()
recallInfoList - reducer
infoList 해당 메세지 갱신
대화방 갱신
대화방 eventList 갱신
대화방 새로고침
액션 생성
대화 삭제
대화 수정 -> 대화 회수
이펙트
대화방 새로고침
info2 호출 파라미터
roomId, isDetail: false, localeCode: loginRes.localeCode
리듀서
room2Success
infoRequest
roomId: string
isDetail: boolean
localeCode
InfoResponse
roomId: string;
// /**
// * Refresh of Room request
// */
// export const roomRefresh = createAction(
// '[ucap::chat::room] room refresh ',
// props<RoomRequest>()
// );
// export const roomRefreshFailure = createAction(
// '[ucap::chat::room] room refresh Failure',
// props<{ error: any }>()
// );
roomRefresh$ = createEffect(() => {
return this.actions$.pipe(
ofType(roomRefresh),
switchMap((action) => {
const req = action.req;
return this.roomProtocolService.info2(req).pipe(
map((res) =>
room2Success({
roomInfo: res.roomInfo,
roomUserInfo: res.roomUserInfo
})
),
catchError((error) => of(roomFailure({ error })))
);
})
);
});

View File

@ -0,0 +1,234 @@
import { createAction, props } from '@ngrx/store';
import {
Info,
InfoRequest as EventInfoRequest,
InfoResponse as EventInfoResponse,
SendRequest as SendEventRequest,
SendResponse as SendEventResponse,
ReadRequest,
DelRequest,
DelResponse,
CancelRequest,
CancelResponse,
SendNotification,
ReadNotification,
CancelNotification,
DelNotification,
EventJson,
ReadResponse,
SendRequest
} from '@ucap/protocol-event';
import {
InfoRequest as FileInfoRequest,
InfoResponse as FileInfoResponse,
FileDownloadInfo,
FileInfo
} from '@ucap/protocol-file';
/**
* retrieve list of event
*/
export const events = createAction(
'[ucap::chat::chatting] events',
props<{ req: EventInfoRequest }>()
);
/**
* Success of events request
*/
export const eventsSuccess = createAction(
'[ucap::chat::chatting] events Success',
props<{
eventInfoList: Info<EventJson>[];
res: EventInfoResponse;
remainEvent: boolean;
}>()
);
/**
* Failure of events request
*/
export const eventsFailure = createAction(
'[ucap::chat::chatting] events Failure',
props<{ roomId: string; error: any }>()
);
/**
* retrieve list of event
*/
export const moreEvents = createAction(
'[ucap::chat::chatting] events more',
props<{ roomId: string }>()
);
/**
* retrieve list of file information
*/
export const fileInfos = createAction(
'[ucap::chat::chatting] fileInfos',
props<{ req: FileInfoRequest }>()
);
/**
* Success of fileInfos request
*/
export const fileInfosSuccess = createAction(
'[ucap::chat::chatting] fileInfos Success',
props<{
fileInfoList: FileInfo[];
fileInfoCheckList: FileDownloadInfo[];
res: FileInfoResponse;
}>()
);
/**
* Failure of fileInfos request
*/
export const fileInfosFailure = createAction(
'[ucap::chat::chatting] fileInfos Failure',
props<{ roomId: string; error: any }>()
);
/**
* add new event
*/
export const addEvent = createAction(
'[ucap::chat::chatting] addEvent',
props<{
roomId: string;
info: Info<EventJson>;
SVC_TYPE?: number;
SSVC_TYPE?: number;
}>()
);
/**
* add new event Success.
*/
export const addEventSuccess = createAction(
'[ucap::chat::chatting] addEvent Success',
props<{
roomId: string;
info: Info<EventJson>;
}>()
);
export const read = createAction(
'[ucap::chat::chatting] read',
props<ReadRequest>()
);
export const readSuccess = createAction(
'[ucap::chat::chatting] read Success',
props<ReadResponse>()
);
// export const readNotification = createAction(
// '[ucap::chat::chatting] Read Notification',
// props<ReadNotification>()
// );
export const readFailure = createAction(
'[ucap::chat::chatting] read Failure',
props<{ error: any }>()
);
export const send = createAction(
'[ucap::chat::chatting] Send',
props<{ senderSeq: string; req: SendEventRequest }>()
);
export const sendSuccess = createAction(
'[ucap::chat::chatting] Send Success',
props<{
senderSeq: string;
res: SendEventResponse;
}>()
);
export const sendFailure = createAction(
'[ucap::chat::chatting] Send Failure',
props<{ error: any }>()
);
export const sendNotification = createAction(
'[ucap::chat::chatting] Send Notification',
props<{ noti: SendNotification }>()
);
/** 대화 삭제 */
export const del = createAction(
'[ucap::chat::chatting] Delete',
props<DelRequest>()
);
export const delFailure = createAction(
'[ucap::chat::chatting] Delete Failure',
props<{ error: any }>()
);
export const delNotification = createAction(
'[ucap::chat::chatting] Delete Notification || Response',
props<{ noti: DelNotification | DelResponse }>()
);
/** 대화 삭제시 열린 대화방의 대화 내용 갱신 */
export const delEventList = createAction(
'[ucap::chat::chatting] Delete InfoList',
props<{
roomId: string;
eventSeqs: number[];
}>()
);
/** forward */
export const forward = createAction(
'[ucap::chat::chatting] Forward',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);
/** chat forward failure */
export const forwardFailure = createAction(
'[ucap::chat::chatting] Forward failure',
props<{ error: any }>()
);
export const forwardAfterRoomOpen = createAction(
'[ucap::chat::chatting] Forward after room open',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);
export const roomOpenAfterForward = createAction(
'[ucap::chat::chatting] Room open after forward',
props<{
senderSeq: string;
req: SendRequest;
trgtUserSeqs?: string[];
trgtRoomSeq?: string;
}>()
);
/** 대화 회수 */
export const cancel = createAction(
'[ucap::chat::chatting] Cancel',
props<CancelRequest>()
);
export const cancelFailure = createAction(
'[ucap::chat::chatting] Cancel Failure',
props<{ error: any }>()
);
export const cancelNotification = createAction(
'[ucap::chat::chatting] Cancel Notification || Response',
props<{ noti: CancelNotification | CancelResponse }>()
);
/** 대화 회수시 열린 대화방의 대화 내용 갱신 */
export const updateEventList = createAction(
'[ucap::chat::chatting] Update InfoList',
props<{
roomId: string;
eventSeq: number;
sentMessage: string;
}>()
);

View File

@ -0,0 +1,533 @@
import { of } from 'rxjs';
import {
tap,
switchMap,
map,
catchError,
exhaustMap,
concatMap,
withLatestFrom,
debounceTime,
mergeMap
} from 'rxjs/operators';
import { Injectable, Inject } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Dictionary } from '@ngrx/entity';
import { EventProtocolService } from '@ucap/ng-protocol-event';
import {
RoomType,
OpenResponse as CreateResponse,
Open3Response as CreateTimerResponse,
ExitResponse as DeleteResponse,
ExitAllResponse as DeleteMultiResponse
} from '@ucap/protocol-room';
import { RoomProtocolService } from '@ucap/ng-protocol-room';
import { FileProtocolService } from '@ucap/ng-protocol-file';
import * as RoomActions from '../room/actions';
import {
events,
eventsFailure,
eventsSuccess,
read,
readFailure,
readSuccess,
fileInfos,
fileInfosFailure,
fileInfosSuccess,
send,
sendSuccess,
sendFailure,
addEvent,
addEventSuccess,
del,
delNotification,
delFailure,
delEventList,
moreEvents,
forward,
forwardFailure,
forwardAfterRoomOpen,
roomOpenAfterForward,
cancel,
cancelFailure,
cancelNotification,
updateEventList
} from './actions';
import {
InfoRequest,
ReadResponse,
FileType,
EventType,
DelResponse,
SendResponse,
CancelResponse
} from '@ucap/protocol-event';
import { ModuleConfig } from '../../config/module-config';
import { _MODULE_CONFIG } from '../../config/token';
import { Chatting } from './state';
import { ChattingSelector, RoomSelector } from '../state';
import { Router } from '@angular/router';
import { LocaleCode } from '@ucap/core';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { I18nService } from '@ucap/ng-i18n';
@Injectable()
export class Effects {
events$ = createEffect(
() => {
return this.actions$.pipe(
ofType(events),
map((action) => action.req),
switchMap((req) => {
return this.eventProtocolService.info(req).pipe(
map((res) => {
if (!!res && !!res.res) {
const infoList = res.infoList;
this.store.dispatch(
eventsSuccess({
eventInfoList: infoList,
res: res.res,
remainEvent:
infoList.length === req.requestCount ? true : false
})
);
if (req.baseSeq === 0) {
// 최초 이벤트 목록 조회.
// SSVC_TYPE_EVENT_READ_REQ 수행.
const maxSeq = Math.max.apply(
Math,
infoList.map((v) => v.seq)
);
this.store.dispatch(
read({
roomId: req.roomId,
lastReadSeq: Number(maxSeq)
})
);
// File 정보 수집.
this.store.dispatch(
fileInfos({
req: {
roomId: res.res.roomId,
// { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌
type: FileType.All
}
})
);
} else {
}
}
}),
catchError((error) =>
of(eventsFailure({ roomId: req.roomId, error }))
)
);
})
);
},
{ dispatch: false }
);
moreEvents$ = createEffect(
() => {
return this.actions$.pipe(
ofType(moreEvents),
mergeMap(
(action) =>
of(action).pipe(
withLatestFrom(
this.store.pipe(
select(ChattingSelector.eventList, action.roomId)
)
)
),
(action, latestStoreData) => latestStoreData
),
tap(([req, eventList]) => {
if (!!eventList && eventList.length > 0) {
this.store.dispatch(
events({
req: {
roomId: req.roomId,
baseSeq: eventList.sort((a, b) => a.seq - b.seq)[0].seq,
requestCount:
this.moduleConfig?.eventRequestDefaultCount || 50
} as InfoRequest
})
);
}
})
);
},
{ dispatch: false }
);
read$ = createEffect(
() => {
return this.actions$.pipe(
ofType(read),
exhaustMap((req) =>
this.eventProtocolService.read(req).pipe(
map((res: ReadResponse) => {
// room user lastReadEventSeq reset.
this.store.dispatch(readSuccess(res));
// room noReadCount reset.
this.store.dispatch(
RoomActions.updateUnreadCount({
roomId: res.roomId
})
);
}),
catchError((error) => of(readFailure({ error })))
)
)
);
},
{ dispatch: false }
);
fileInfos$ = createEffect(
() => {
return this.actions$.pipe(
ofType(fileInfos),
switchMap((action) => {
return this.fileProtocolService.info(action.req).pipe(
map((res) => {
this.store.dispatch(
fileInfosSuccess({
fileInfoList: res.fileInfos,
fileInfoCheckList: res.fileInfoChecks,
res: res.res
})
);
}),
catchError((error) =>
of(fileInfosFailure({ roomId: action.req.roomId, error }))
)
);
})
);
},
{ dispatch: false }
);
addEvent$ = createEffect(
() => {
return this.actions$.pipe(
ofType(addEvent),
withLatestFrom(
this.store.pipe(
select(
(state: any) =>
state.chat.chatting.chattings.entities as Dictionary<Chatting>
)
),
this.store.pipe(
select((state: any) => state.chat.chatting.activeRoomId)
)
),
tap(([action, chattings, activeRoomId]) => {
// Room in chattings state >> event add
if (!!chattings && !!chattings[action.roomId]) {
if (action.info.type === EventType.File) {
// Retrieve event of FileType in rooms
this.store.dispatch(
fileInfos({
req: {
roomId: action.roomId,
type: FileType.All
}
})
);
}
}
// room > rooms :: finalEvent-Infos refresh
this.store.dispatch(
addEventSuccess({
roomId: action.roomId,
info: action.info
})
);
})
);
},
{ dispatch: false }
);
send$ = createEffect(() =>
this.actions$.pipe(
ofType(send),
concatMap((action) =>
this.eventProtocolService.send(action.req).pipe(
map((res) => {
return sendSuccess({
senderSeq: action.senderSeq,
res
});
}),
catchError((error) => of(sendFailure({ error })))
)
)
)
);
sendSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(sendSuccess),
tap((action) => {
const res = action.res;
this.store.dispatch(
addEvent({
roomId: res.roomId,
info: res.info,
SVC_TYPE: res.SVC_TYPE,
SSVC_TYPE: res.SSVC_TYPE
})
);
})
);
},
{ dispatch: false }
);
/*******************************************************************
* [Room Action watching.]
*******************************************************************/
selectedRoomSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(RoomActions.selectedRoomSuccess),
debounceTime(300),
tap((action) => {
const requestCount = this.moduleConfig?.eventRequestInitCount || 50;
const req: InfoRequest = {
roomId: action.roomId,
baseSeq: 0,
requestCount
};
this.store.dispatch(events({ req }));
})
);
},
{ dispatch: false }
);
del$ = createEffect(() =>
this.actions$.pipe(
ofType(del),
exhaustMap((req) =>
this.eventProtocolService.del(req).pipe(
map((res: DelResponse) => {
return delNotification({ noti: res });
}),
catchError((error) => of(delFailure({ error })))
)
)
)
);
delNotification$ = createEffect(
() => {
return this.actions$.pipe(
ofType(delNotification),
map((action) => action.noti),
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
tap(([noti, activeRoomId]) => {
// 현재 방이 오픈되어 있으면 방내용 갱신
const roomId = noti.roomId;
if (!!activeRoomId && activeRoomId === roomId) {
this.store.dispatch(
delEventList({ roomId, eventSeqs: [noti.eventSeq] })
);
}
// 대화 > 리스트의 항목 갱신
this.store.dispatch(
RoomActions.room({
req: {
roomId: noti.roomId,
isDetail: false,
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
}
})
);
})
);
},
{ dispatch: false }
);
forward$ = createEffect(
() => {
return this.actions$.pipe(
ofType(forward),
tap((action) => {
if (!!action.trgtRoomSeq) {
// 대화전달 후 방오픈. Exist roomSeq.
if (action.req.eventType === EventType.File) {
// file share request action
} else {
this.store.dispatch(roomOpenAfterForward(action));
}
} else if (!!action.trgtUserSeqs && action.trgtUserSeqs.length > 0) {
// 방오픈 후 대화전달.
this.store.dispatch(forwardAfterRoomOpen(action));
}
})
);
},
{ dispatch: false }
);
forwardAfterRoomOpen$ = createEffect(
() => {
let createRes: CreateResponse;
return this.actions$.pipe(
ofType(forwardAfterRoomOpen),
exhaustMap((actionReq) => {
return this.roomProtocolService
.open({ divCd: 'forwardOpen', userSeqs: actionReq.trgtUserSeqs })
.pipe(
map((res: CreateResponse) => {
createRes = res;
this.store.dispatch(RoomActions.createSuccess({ res }));
}),
map(() => {
if (actionReq.req.eventType === EventType.File) {
// file share request action
} else {
this.store.dispatch(
send({
senderSeq: actionReq.senderSeq,
req: {
roomId: createRes.roomId,
eventType: actionReq.req.eventType,
sentMessage: actionReq.req.sentMessage
}
})
);
}
}),
catchError((error) => of(RoomActions.createFailure({ error })))
);
})
);
},
{ dispatch: false }
);
roomOpenAfterForward$ = createEffect(
() => {
return this.actions$.pipe(
ofType(roomOpenAfterForward),
exhaustMap((action) => {
return this.eventProtocolService
.send({
roomId: action.trgtRoomSeq,
eventType: action.req.eventType,
sentMessage: action.req.sentMessage
})
.pipe(
map((res: SendResponse) => {
this.store.dispatch(
addEvent({
roomId: res.roomId,
info: res.info,
SVC_TYPE: res.SVC_TYPE,
SSVC_TYPE: res.SSVC_TYPE
})
);
this.store.dispatch(
RoomActions.selectedRoom({
roomId: res.roomId,
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
})
);
}),
catchError((error) => of(RoomActions.createFailure({ error })))
);
})
);
},
{ dispatch: false }
);
cancel$ = createEffect(() =>
this.actions$.pipe(
ofType(cancel),
exhaustMap((req) =>
this.eventProtocolService.cancel(req).pipe(
map((res: CancelResponse) => {
return delNotification({ noti: res });
}),
catchError((error) => of(delFailure({ error })))
)
)
)
);
cancelNotification$ = createEffect(
() => {
return this.actions$.pipe(
ofType(cancelNotification),
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
tap(([action, activeRoomId]) => {
// 현재 방이 오픈되어 있으면 방내용 갱신
if (
!!activeRoomId &&
activeRoomId.localeCompare(action.noti.roomId) === 0
) {
this.store.dispatch(
updateEventList({
roomId: action.noti.roomId,
eventSeq: action.noti.eventSeq,
sentMessage: this.i18nService.t('event.recalled')
})
);
}
// 대화 > 리스트의 항목 갱신
this.store.dispatch(
RoomActions.room({
req: {
roomId: action.noti.roomId,
isDetail: false,
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
}
})
);
})
);
},
{ dispatch: false }
);
constructor(
private actions$: Actions,
private store: Store<any>,
private router: Router,
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private roomProtocolService: RoomProtocolService,
private eventProtocolService: EventProtocolService,
private fileProtocolService: FileProtocolService,
private i18nService: I18nService
) {
this.i18nService.setDefaultNamespace('chat');
}
}

View File

@ -0,0 +1,253 @@
import { createReducer, on } from '@ngrx/store';
import {
initialState,
adapterChatting,
adapterEventList,
Chatting,
adapterFileInfoList,
adapterFileInfoCheckList
} from './state';
import * as RoomActions from '../room/actions';
import {
eventsSuccess,
eventsFailure,
fileInfosSuccess,
fileInfosFailure,
addEvent,
delEventList,
updateEventList
} from './actions';
import { Info, EventJson, EventType } from '@ucap/protocol-event';
export const reducer = createReducer(
initialState,
on(eventsSuccess, (state, action) => {
const roomId = action.res.roomId;
const chatting = state.chattings.entities[roomId] || {};
let trgtChatting: Chatting = {
roomId,
eventListProcessing: false,
eventList: adapterEventList.getInitialState(),
eventStatus: null,
remainEvent: false,
fileInfoListProcessing: false,
fileInfoList: adapterFileInfoList.getInitialState(),
fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
fileInfoSyncDate: '',
...chatting
};
trgtChatting = {
...trgtChatting,
eventList: adapterEventList.upsertMany(action.eventInfoList, {
...trgtChatting.eventList
}),
eventStatus: action.res,
remainEvent: action.remainEvent,
eventListProcessing: false
};
return {
...state,
chattings: adapterChatting.upsertOne(trgtChatting, {
...state.chattings
})
};
}),
on(eventsFailure, (state, action) => {
const roomId = action.roomId;
const chatting = state.chattings.entities[roomId];
let trgtChatting: Chatting;
if (!!chatting) {
trgtChatting = {
...chatting,
eventListProcessing: false
};
}
return {
...state,
chattings: adapterChatting.upsertOne(trgtChatting, {
...state.chattings
})
};
}),
on(fileInfosSuccess, (state, action) => {
const roomId = action.res?.roomId;
if (!roomId) {
return state;
}
const chatting = state.chattings.entities[roomId] || {};
let trgtChatting: Chatting = {
roomId,
eventListProcessing: false,
eventList: adapterEventList.getInitialState(),
eventStatus: null,
remainEvent: false,
fileInfoListProcessing: false,
fileInfoList: adapterFileInfoList.getInitialState(),
fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
fileInfoSyncDate: '',
...chatting
};
const fileInfoList = action.fileInfoList;
const fileInfoCheckList = action.fileInfoCheckList;
trgtChatting = {
...trgtChatting,
fileInfoList: !!fileInfoList
? adapterFileInfoList.upsertMany(fileInfoList, {
...trgtChatting.fileInfoList
})
: trgtChatting.fileInfoList,
fileInfoCheckList: !!fileInfoCheckList
? adapterFileInfoCheckList.upsertMany(fileInfoCheckList, {
...trgtChatting.fileInfoCheckList
})
: trgtChatting.fileInfoCheckList,
fileInfoListProcessing: false
};
return {
...state,
chattings: adapterChatting.upsertOne(trgtChatting, {
...state.chattings
})
};
}),
on(fileInfosFailure, (state, action) => {
const roomId = action.roomId;
const chatting = state.chattings.entities[roomId];
let trgtChatting: Chatting;
if (!!chatting) {
trgtChatting = {
...chatting,
fileInfoListProcessing: false
};
}
return {
...state,
chattings: adapterChatting.upsertOne(trgtChatting, {
...state.chattings
})
};
}),
on(addEvent, (state, action) => {
const roomId = action.roomId;
const eventInfo = action.info;
const chatting = state.chattings.entities[roomId];
if (!!chatting) {
return {
...state,
chattings: adapterChatting.upsertOne(
{
...chatting,
eventList: adapterEventList.upsertOne(eventInfo, {
...chatting.eventList
})
},
{ ...state.chattings }
)
};
} else {
return state;
}
}),
/*******************************************************************
* [Room Action watching.]
*******************************************************************/
on(RoomActions.selectedRoomSuccess, (state, action) => {
return {
...state,
activeRoomId: action.roomId
};
}),
on(RoomActions.clearSelectedRoom, (state, action) => {
if (action.roomId === state.activeRoomId) {
return {
...state,
activeRoomId: null
};
} else {
return state;
}
}),
on(RoomActions.close, (state, action) => {
const roomIds = action.roomIds;
return {
...state,
chattings: adapterChatting.removeMany(roomIds, { ...state.chattings })
};
}),
on(delEventList, (state, action) => {
const roomId = action.roomId;
const chatting = state.chattings.entities[roomId];
return {
...state,
chattings: adapterChatting.upsertOne(
{
...chatting,
eventList: adapterEventList.removeMany(action.eventSeqs, {
...chatting.eventList
})
},
{ ...state.chattings }
)
};
}),
on(updateEventList, (state, action) => {
const roomId = action.roomId;
const eventSeq = action.eventSeq;
const sentMessage = action.sentMessage;
const chatting = state.chattings.entities[roomId];
const statusEventInfo: Info<EventJson> = {
...chatting.eventList[eventSeq],
type: EventType.RecalledMessage,
sentMessage
};
return {
...state,
chattings: adapterChatting.upsertOne(
{
...chatting,
eventList: adapterEventList.updateOne(
{ id: eventSeq, changes: statusEventInfo },
{
...chatting.eventList
}
)
},
{ ...state.chattings }
)
};
})
);

View File

@ -0,0 +1,93 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
>
<div appLayoutsDefaultDialog="header">
{{ 'dialog.title.newChatRoom' | ucapI18n }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<!-- search start-->
<div>
<app-organization-search-for-tenant
placeholder="이름 부서명, 전화번호, 이메일"
[(searchData)]="companySearchData"
(canceled)="onCanceled()"
class="select-user-section-search"
>
</app-organization-search-for-tenant>
</div>
<!-- search end-->
<div *ngIf="!isSearch">
<mat-tab-group mat-stretch-tabs class="tap-container tab_num2">
<!--그룹-->
<mat-tab>
<ng-template mat-tab-label>
<!-- <button class="icon-button">
<i class="mid mid-24 mdi-account-multiple"></i>
</button> -->
<p>그룹</p>
</ng-template>
<div fxFlexFill class="select-tap">
<app-group-expansion
fxFlexFill
[isDialog]="isDialog"
[selectedUserList]="selectedUserList"
[fixedUserList]="fixedUserList"
[checkable]="checkable"
(toggleCheckUser)="onToggleCheckUser($event)"
(toggleCheckGroup)="onToggleCheckGroup($event)"
class="select-user-tap-group-expansion"
></app-group-expansion>
</div>
</mat-tab>
<!--대화방 리스트-->
<ng-template mat-tab-label>
<p>대화방 리스트</p>
</ng-template>
<div fxFlexFill class="select-tap">
<div class="roomList" *ngFor="let room of roomList">
<ucap-chat-room-list-item-01
[roomInfo]="node.roomInfo"
[roomName]="getRoomName(node.roomInfo)"
[profileImageRoot]="versionInfo2Res?.profileRoot"
[defaultProfileImage]="defaultProfileImage"
[profileImage]="getRoomProfileImage(node.roomInfo)"
[checkable]="true"
(toggleItem)="onToggleItem($event)"
></ucap-chat-room-list-item-01>
</div>
</div>
<mat-tab>
</mat-tab>
</mat-tab-group>
</div>
<div *ngIf="!!isSearch"></div>
<app-group-profile-list
[searchData]="companySearchData"
[selectedUser]="selectedUserList"
[checkable]="checkable"
[isDialog]="isDialog"
(toggleCheck)="onToggleCheckUser($event)"
></app-group-profile-list>
</div>
<div appLayoutsDefaultDialog="action">
<button mat-button (click)="onCancel()">
{{
(stepper.selectedIndex === 0
? 'dialog.button.cancel'
: 'dialog.button.previous'
) | ucapI18n
}}
</button>
<button
mat-button
*ngIf="stepper.selectedIndex === 0"
(click)="onConfirm()"
>
{{ 'dialog.button.selectRoomUser' | ucapI18n }}
</button>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,9 @@
.dialog-container {
width: 100%;
height: 100%;
.dialog-body {
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ForwardDialogComponent } from './forward.dialog.component';
describe('app::ui-chat::ForwardDialogComponent', () => {
let component: ForwardDialogComponent;
let fixture: ComponentFixture<ForwardDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ForwardDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ForwardDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,123 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
Input
} from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { MatStepper } from '@angular/material/stepper';
import { I18nService } from '@ucap/ng-i18n';
import { GroupActions } from '@ucap/ng-store-group';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import { environment } from '@environments';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
export interface ForwardDialogData {}
export interface ForwardDialogResult {
userSeqs: string[];
isTimer: boolean | undefined;
}
@Component({
selector: 'app-dialog-chat-forward',
templateUrl: './forward.dialog.component.html',
styleUrls: ['./forward.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ForwardDialogComponent implements OnInit, OnDestroy {
currentStep = 0;
maxChatRoomUser: number;
isTimer: boolean | undefined;
selectedUserList: UserInfoTypes[] = [];
constructor(
public dialogRef: MatDialogRef<ForwardDialogData, ForwardDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: ForwardDialogData,
private i18nService: I18nService,
public dialog: MatDialog,
private changeDetectorRef: ChangeDetectorRef
) {
this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser;
}
ngOnInit(): void {}
ngOnDestroy(): void {}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
onCancel() {
}
onConfirm() {
}
onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) {
const i = this.selectedUserList.findIndex(
(u) => u.seq === data.userInfo.seq
);
if (data.checked) {
if (-1 === i) {
this.selectedUserList = [...this.selectedUserList, data.userInfo];
}
} else {
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => u.seq !== data.userInfo.seq
);
}
}
}
onChangeGroupList(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter((user) => user.seq === item.seq)
.length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq)
.length === 0
);
}
}
}

View File

@ -0,0 +1,31 @@
<div class="ucap-chat-room-list-item2">
<div class="ucap-chat-room-list-item2-profile-image">
<div class="profile-image">
<img
ucapImage
[base]="profileImageRoot"
[path]="profileImage"
[default]="defaultProfileImage"
/>
</div>
</div>
<div class="ucap-chat-room-list-item2-info">
<div class="roomName">
<div class="chat-subject">{{ roomName }}</div>
<div *ngIf="roomInfo.roomType === RoomType.Multi" class="chat-amount">
<strong>({{ roomInfo.joinUserCount }})</strong>
</div>
</div>
<div class="lastMessage">{{ roomInfo.finalEventMessage }}</div>
</div>
<mat-checkbox
*ngIf="checkable"
#checkbox
[checked]="checked"
(change)="onToggleItem(checkbox.checked)"
(click)="$event.stopPropagation()"
class="group-check"
>
</mat-checkbox>
</div>

View File

@ -0,0 +1,34 @@
.ucap-chat-room-list-item2 {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-content: center;
justify-content: space-between;
.ucap-chat-room-list-item2-profile-image {
display: inline-flex;
position: relative;
align-self: center;
}
.ucap-chat-room-list-item2-info {
display: flex;
flex-direction: column;
justify-content: center;
.roomName {
display: flex;
flex-direction: row;
.ico-chat-list {
.ico-off {
&-false {
display: inline-block;
}
&-true {
display: none;
}
}
}
}
}
}

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RoomListItem02Component } from './room-list-item-02.component';
describe('ucap::chat::RoomListItem01Component', () => {
let component: RoomListItem02Component;
let fixture: ComponentFixture<RoomListItem02Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [RoomListItem02Component]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RoomListItem02Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,26 @@
import { moduleMetadata } from '@storybook/angular';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ChatUiModule } from '../chat-ui.module';
import { RoomListItem02Component } from './room-list-item-02.component';
export default {
title: 'ui-chat::RoomListItem02Component',
component: RoomListItem02Component,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, ChatUiModule],
providers: []
})
]
};
export const Default = () => ({
component: RoomListItem02Component,
props: {}
});

View File

@ -0,0 +1,20 @@
@import '~@ucap/ng-ui-material/material';
@mixin ucap-chat-room-list-item-02-theme($theme) {
$is-dark-theme: map-get($theme, is-dark);
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
.ucap-chat-room-list-item-02-container {
border-color: mat-color($foreground, secondary-text);
}
}
@mixin ucap-chat-room-list-item-02-typography($config) {
.ucap-chat-room-list-item-01-container {
font-family: mat-font-family($config);
}
}

View File

@ -0,0 +1,63 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
OnDestroy,
EventEmitter,
Output,
ChangeDetectorRef
} from '@angular/core';
import { RoomInfo, RoomType } from '@ucap/protocol-room';
@Component({
selector: 'ucap-chat-room-list-item-02',
templateUrl: './room-list-item-02.component.html',
styleUrls: ['./room-list-item-02.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RoomListItem02Component implements OnInit, OnDestroy {
@Input()
roomInfo: RoomInfo;
@Input()
profileImageRoot: string;
@Input()
defaultProfileImage: string;
@Input()
profileImage: string;
@Input()
roomName: string;
@Input()
checkable = false;
@Input()
checked = false;
@Output()
toggleItem = new EventEmitter<{
checked: boolean;
roomInfo: RoomInfo;
}>();
RoomType = RoomType;
constructor(private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit(): void {}
ngOnDestroy(): void {}
onToggleItem(value: boolean): void {
this.toggleItem.emit({
checked: value,
roomInfo: this.roomInfo
});
this.changeDetectorRef.detectChanges();
}
}

View File

@ -0,0 +1,530 @@
import { of } from 'rxjs';
import {
catchError,
map,
switchMap,
exhaustMap,
withLatestFrom,
tap,
debounceTime
} from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import {
OpenResponse as CreateResponse,
Open3Response as CreateTimerResponse,
ExitResponse as DeleteResponse,
ExitAllResponse as DeleteMultiResponse,
UpdateResponse,
InviteResponse,
ExitForcingResponse,
UpdateTimerSetResponse,
InfoRequest
} from '@ucap/protocol-room';
import { RoomProtocolService } from '@ucap/ng-protocol-room';
import { SyncProtocolService } from '@ucap/ng-protocol-sync';
import { LoginActions, LoginSelector } from '@ucap/ng-store-authentication';
import * as ChattingAction from '../Chatting/actions';
import { RoomSelector, ChattingSelector } from '../state';
import {
rooms,
room,
roomFailure,
inviteNotification,
exitNotification,
excludeUser,
excludeUserSuccess,
close,
delSuccess,
create,
createSuccess,
createFailure,
createTimer,
createTimerSuccess,
createTimerFailure,
del,
delFailure,
update,
updateSuccess,
updateFailure,
open,
openSuccess,
closeSuccess,
invite,
inviteSuccess,
inviteFailure,
expel,
expelSuccess,
expelFailure,
updateTimeRoomInterval,
updateTimeRoomIntervalSuccess,
updateTimeRoomIntervalFailure,
rooms2Success,
rooms2Failure,
delMulti,
delMultiSuccess,
delMultiFailure,
selectedRoom,
room2Success,
selectedRoomSuccess,
clearSelectedRoom
} from './actions';
import { LocaleCode } from '@ucap/core';
import { PresenceActions } from '@ucap/ng-store-organization';
import { I18nService } from '@ucap/ng-i18n';
@Injectable()
export class Effects {
sessionCreatedForRooms$ = createEffect(() => {
return this.actions$.pipe(
ofType(LoginActions.sessionCreated),
map((action) => rooms({ localeCode: action.loginSession.localeCode }))
);
});
selectedRoom$ = createEffect(
() => {
return this.actions$.pipe(
ofType(selectedRoom),
debounceTime(300),
tap((action) => {
const req: InfoRequest = {
...action,
isDetail: false
};
// retrieve room info
this.roomProtocolService
.info2(req)
.pipe(
map((res) => {
let isJoinRoom = true;
if (!res.roomInfo || !res.roomInfo.isJoinRoom) {
isJoinRoom = false;
}
if (!!isJoinRoom) {
this.store.dispatch(
selectedRoomSuccess({
roomId: req.roomId,
roomInfo2Res: res
})
);
// Buddy Presence
const targetUserInfos = req.isDetail
? res.roomUserInfo.userInfoList.map(
(userInfo) => userInfo.seq + ''
)
: res.roomUserInfo.userInfoShortList.map(
(userInfo) => userInfo.seq + ''
);
if (!!targetUserInfos && targetUserInfos.length > 0) {
this.store.dispatch(
PresenceActions.bulkInfo({
divCd: 'roomBulk',
userSeqs: targetUserInfos
})
);
}
} else {
// is not join room. so, redirect chat main.
this.store.dispatch(
clearSelectedRoom({ roomId: req.roomId })
);
}
}),
catchError((error) => of(roomFailure({ error })))
)
.subscribe();
// retrieve event info >> chatting.effect.selectedRoom$
})
);
},
{ dispatch: false }
);
selectedRoomSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(selectedRoomSuccess),
tap((action) => {
// room info success reduce.
this.store.dispatch(
room2Success({
roomInfo: action.roomInfo2Res.roomInfo,
roomUserInfo: action.roomInfo2Res.roomUserInfo
})
);
})
);
},
{ dispatch: false }
);
rooms$ = createEffect(() => {
return this.actions$.pipe(
ofType(rooms),
withLatestFrom(this.store.pipe(select(RoomSelector.roomsSyncDate))),
switchMap(([action, syncDate]) => {
// // CASE :: RoomUser Data 중 Detail data 만 수집.
// return this.syncProtocolService
// .room({ syncDate, localeCode: action.localeCode })
// .pipe(
// map((res) => {
// return roomsSuccess({
// roomList: res.roomList,
// roomUserInfoMap: res.roomUserInfoMap,
// syncDate: res.res.syncDate
// });
// }),
// catchError((error) => of(roomsFailure({ error })))
// );
// CASE :: RoomUser Data 중 Detail data, Short data 수집.
return this.syncProtocolService
.room2({ syncDate, localeCode: action.localeCode })
.pipe(
map((res) => {
return rooms2Success({
roomList: res.roomList,
roomUserInfoMap: res.roomUserInfoMap,
syncDate: res.res.syncDate
});
}),
catchError((error) => of(rooms2Failure({ error })))
);
})
);
});
room$ = createEffect(() => {
return this.actions$.pipe(
ofType(room),
switchMap((action) => {
const req = action.req;
// // CASE :: RoomUser Data 중 Detail data 만 수집.
// return this.roomProtocolService.info(req).pipe(
// map((res) =>
// roomSuccess({
// roomInfo: res.roomInfo,
// userInfoList: res.userInfoList
// })
// ),
// catchError((error) => of(roomFailure({ error })))
// );
// CASE :: RoomUser Data 중 Detail data, Short data 수집.
return this.roomProtocolService.info2(req).pipe(
map((res) =>
room2Success({
roomInfo: res.roomInfo,
roomUserInfo: res.roomUserInfo
})
),
catchError((error) => of(roomFailure({ error })))
);
})
);
});
create$ = createEffect(
() => {
return this.actions$.pipe(
ofType(create),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.open(req).pipe(
map((res: CreateResponse) => {
this.store.dispatch(createSuccess({ res }));
// this.router.navigate(
// [
// 'chat',
// {
// outlets: { content: 'chatroom' }
// }
// ],
// {
// queryParams: { roomId: res.roomId }
// }
// );
// if (!res.newRoom) {
// } else {
// }
}),
catchError((error) => of(createFailure({ error })))
);
})
);
},
{ dispatch: false }
);
createTimer$ = createEffect(() =>
this.actions$.pipe(
ofType(createTimer),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.open3(req).pipe(
map((res: CreateTimerResponse) => {
return createTimerSuccess({ res });
}),
catchError((error) => of(createTimerFailure({ error })))
);
})
)
);
del$ = createEffect(() =>
this.actions$.pipe(
ofType(del),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.exit(req).pipe(
switchMap((res: DeleteResponse) => [
// clear activeRoomId
clearSelectedRoom({ roomId: res.roomId }),
// close room, clear chatting
close({ roomIds: [res.roomId] }),
// clear room in rooms.
delSuccess({ res })
]),
catchError((error) => of(delFailure({ error })))
);
})
)
);
delMulti$ = createEffect(() =>
this.actions$.pipe(
ofType(delMulti),
map((action) => action.req),
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
exhaustMap(([req, activeRoomId]) => {
const existActiveRoomId = req.roomIds.find(
(roomId) => roomId === activeRoomId
);
return this.roomProtocolService.exitAll(req).pipe(
switchMap((res: DeleteMultiResponse) => {
if (!!existActiveRoomId) {
return [
// clear selected room
clearSelectedRoom({ roomId: existActiveRoomId }),
// close room, clear chatting
close({ roomIds: res.roomIds }),
// clear room in rooms.
delMultiSuccess({ res })
];
} else {
return [
// close room, clear chatting
close({ roomIds: res.roomIds }),
// clear room in rooms.
delMultiSuccess({ res })
];
}
}),
catchError((error) => of(delMultiFailure({ error })))
);
})
)
);
update$ = createEffect(() =>
this.actions$.pipe(
ofType(update),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.update(req).pipe(
map((res: UpdateResponse) => {
return updateSuccess({ res });
}),
catchError((error) => of(updateFailure({ error })))
);
})
)
);
excludeUser$ = createEffect(() => {
return this.actions$.pipe(
ofType(excludeUser),
map((action) =>
excludeUserSuccess({
roomId: action.roomId,
userSeqs: action.userSeqs
})
)
);
});
open$ = createEffect(() => {
return this.actions$.pipe(
ofType(open),
map((action) => openSuccess({ roomIds: [...action.roomIds] }))
);
});
close$ = createEffect(() => {
return this.actions$.pipe(
ofType(close),
map((action) => closeSuccess({ roomIds: [...action.roomIds] }))
);
});
invite$ = createEffect(() =>
this.actions$.pipe(
ofType(invite),
exhaustMap((action) => {
const req = action.req;
const localeCode = action.localeCode;
return this.roomProtocolService.invite(req).pipe(
switchMap((res: InviteResponse) => {
return [
inviteSuccess({ res }),
room({
req: {
roomId: req.roomId,
isDetail: true,
localeCode
}
})
];
}),
catchError((error) => of(inviteFailure({ error })))
);
})
)
);
expel$ = createEffect(() =>
this.actions$.pipe(
ofType(expel),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.exitForcing(req).pipe(
map((res: ExitForcingResponse) => {
return expelSuccess({ res });
}),
catchError((error) => of(expelFailure({ error })))
);
})
)
);
updateTimeRoomInterval$ = createEffect(() =>
this.actions$.pipe(
ofType(updateTimeRoomInterval),
map((action) => action.req),
exhaustMap((req) => {
return this.roomProtocolService.updateTimerSet(req).pipe(
map((res: UpdateTimerSetResponse) => {
return updateTimeRoomIntervalSuccess({ res });
}),
catchError((error) => of(updateTimeRoomIntervalFailure({ error })))
);
})
)
);
/**
* @discription Call by notifications case in SSVC_TYPE_ROOM_INVITE_RES, SSVC_TYPE_ROOM_INVITE_NOTI
* 1. roomlist .( .)
* 2. roomlist .
*/
inviteNotification$ = createEffect(
() => {
return this.actions$.pipe(
ofType(inviteNotification),
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
map(([action, roomList]) => {
const roomId = action.noti.roomId;
if (!!roomList && roomList.length > 0) {
if (roomList.some((roomInfo) => roomId === roomInfo.roomId)) {
this.store.dispatch(
room({
req: {
roomId,
isDetail: true,
localeCode: action.localeCode
}
})
);
}
}
})
);
},
{
dispatch: false
}
);
exitNotification$ = createEffect(() => {
return this.actions$.pipe(
ofType(exitNotification),
withLatestFrom(this.store.pipe(select(LoginSelector.loginRes))),
switchMap(([action, loginRes]) => {
if (loginRes.userSeq + '' === action.senderSeq + '') {
return [
close({ roomIds: [action.roomId] }),
clearSelectedRoom({ roomId: action.roomId }),
delSuccess({
res: { roomId: action.roomId }
})
];
} else {
return [
excludeUser({
roomId: action.roomId,
userSeqs: [action.senderSeq]
})
];
}
})
);
});
/*******************************************************************
* [Chatting Action watching.]
*******************************************************************/
addEventSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(ChattingAction.addEventSuccess),
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
map(([action, roomList]) => {
const roomId = action.roomId;
if (!roomList.find((roomInfo) => roomInfo.roomId === roomId)) {
this.store.dispatch(
rooms({
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
})
);
}
})
);
},
{ dispatch: false }
);
constructor(
private actions$: Actions,
private store: Store<any>,
private syncProtocolService: SyncProtocolService,
private roomProtocolService: RoomProtocolService,
private i18nService: I18nService
) {}
}

Binary file not shown.

View File

@ -0,0 +1,15 @@
버그
동료 삭제, 그룹 삭제 시 프로토콜 절차 정상적으로 진행되나 새로고침 시 기존 데이터가 다시 로드됨
팝업 테넌트 검색 시 결과에 대한 체크박스 체크 후 다시 검색 시 체크가 됨
추가 기능
그룹
팝업 조직도 사용자 선택
팝업 사용자 선택 창 사이즈 조절 시 위치 변경
그룹 추가 팝업 "그룹지정 완료" 시
사용자 선택 x
그룹 선택 x
완료 버튼 비활성화
채팅
노티피케이션 연동

View File