0528 sync

This commit is contained in:
Park Byung Eun 2020-05-28 21:52:25 +09:00
parent 02c2645e01
commit 176eb2bbd6
45 changed files with 1230 additions and 360 deletions

37
package-lock.json generated
View File

@ -3314,9 +3314,9 @@
"dev": true "dev": true
}, },
"@ucap/api-common": { "@ucap/api-common": {
"version": "0.0.3", "version": "0.0.5",
"resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api-common/-/api-common-0.0.3.tgz", "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api-common/-/api-common-0.0.5.tgz",
"integrity": "sha512-rifcCToIXdWZb9R3UXu2bXDqj/KBX3xfxHPFgx+Wp1TBUh6d+xszNYN7+mZbDKKSOYUgpVeuaF8leKkTwXUh9g==", "integrity": "sha512-7V/Ea4FbLIKE+/ti414Spm6PQS3ZDcsHTZStLLYkMQrU7x+GbrGnHGOcvzzAQtE2SmpoNd3q6JzoYY+CK1eCkA==",
"dev": true "dev": true
}, },
"@ucap/api-external": { "@ucap/api-external": {
@ -3393,8 +3393,8 @@
"dev": true "dev": true
}, },
"@ucap/ng-core": { "@ucap/ng-core": {
"version": "file:pack/ucap-ng-core-0.0.1.tgz", "version": "file:pack/ucap-ng-core-0.0.7.tgz",
"integrity": "sha512-okuTcVh/9VkF9dA1d/nsPuLL8ft2+4E77mgqfuPI2mI6IA7udQpC+hdMtyqtAN4LmT39iF1VGFv7UJYRK7fN7w==", "integrity": "sha512-ZC6LE3A0bg+REGbzDI/i1ad7mGpKsw6X0UtZ+Q8TUthHNv0DfWEieHFCgfYTRY1u022XyQ4ViOsrq9KunU1vfw==",
"dev": true "dev": true
}, },
"@ucap/ng-i18n": { "@ucap/ng-i18n": {
@ -3508,8 +3508,8 @@
"dev": true "dev": true
}, },
"@ucap/ng-store-chat": { "@ucap/ng-store-chat": {
"version": "file:pack/ucap-ng-store-chat-0.0.13.tgz", "version": "file:pack/ucap-ng-store-chat-0.0.16.tgz",
"integrity": "sha512-o+BCCSMxneUenRHEW47sSY22+Zt3lyr202Lg4bub9OVRbW5CVohHez8H+JwK+w+Lf8KbqG32V1ZjKLGclTpboA==", "integrity": "sha512-Rwupo2Gqa+4+0ANBZLtup+mdlTU42Tj5gkGnJJ86Vylo4xQcdR+PUdedWZ/lG7DyzrfBc8ON80aUPRikpWUCow==",
"dev": true "dev": true
}, },
"@ucap/ng-store-group": { "@ucap/ng-store-group": {
@ -3528,13 +3528,8 @@
"dev": true "dev": true
}, },
"@ucap/ng-ui-authentication": { "@ucap/ng-ui-authentication": {
"version": "file:pack/ucap-ng-ui-authentication-0.0.24.tgz", "version": "file:pack/ucap-ng-ui-authentication-0.0.25.tgz",
"integrity": "sha512-6QMJ8dieTnbPANsBzg2Ll3HH5q6Bzl2iSM19yHq8Ct7XOmElrYqrEZmxbDyYO+aCXIAwd2t7vu+rTsHfz3XOQg==", "integrity": "sha512-/KgMR8JHcnOm+XwMQVhHGud61Hivi/fX0NucxUglsOguYiWL6Ms61FdRezB3wuKs5h/5+zI4QIWLdWdT8GT/Dg==",
"dev": true
},
"@ucap/ng-ui-chat": {
"version": "file:pack/ucap-ng-ui-chat-0.0.9.tgz",
"integrity": "sha512-6qvzcTuylkxVjsqajsLW15laOyOskxVMy238/Ju1yYvwCRyHygwS1i67APoG5tv+SWu+l38f9uWIqzfy7WYHkQ==",
"dev": true "dev": true
}, },
"@ucap/ng-ui-group": { "@ucap/ng-ui-group": {
@ -3544,13 +3539,7 @@
}, },
"@ucap/ng-ui-material": { "@ucap/ng-ui-material": {
"version": "file:pack/ucap-ng-ui-material-0.0.4.tgz", "version": "file:pack/ucap-ng-ui-material-0.0.4.tgz",
"integrity": "sha512-ySPULAbP+nQ65hBG2VWZ2H5Hr7muuTGGNXs6A+S3lsxLaW452wM3GNyUBhvUopr8LaSsoOPpp4nK1JeC0fG6pA==", "integrity": "sha512-ySPULAbP+nQ65hBG2VWZ2H5Hr7muuTGGNXs6A+S3lsxLaW452wM3GNyUBhvUopr8LaSsoOPpp4nK1JeC0fG6pA=="
"dev": true
},
"@ucap/ng-ui-organization": {
"version": "file:pack/ucap-ng-ui-organization-0.0.55.tgz",
"integrity": "sha512-vfpKd3fbd+I0Od8aB2nIFfjuI7wj3Ziu/uiTEmZxKwZy7uZrNYm59BPbctKW3AQsQ4UtnLofhlBbAA7e9pT80Q==",
"dev": true
}, },
"@ucap/ng-ui-skin-default": { "@ucap/ng-ui-skin-default": {
"version": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz", "version": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz",
@ -3640,9 +3629,9 @@
"dev": true "dev": true
}, },
"@ucap/protocol-room": { "@ucap/protocol-room": {
"version": "0.0.5", "version": "0.0.6",
"resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-room/-/protocol-room-0.0.5.tgz", "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-room/-/protocol-room-0.0.6.tgz",
"integrity": "sha512-RSzLtnz5JVeDz9Y8gP17+LO6OG2NtLIPpW+JuHnqhfJyB93v2QmqqK5T7NbGYok72jRp4m7cJZEp1tLNYCKcmA==", "integrity": "sha512-PaEztUnZgmsH/Vo4JijJxpu9DBSA4SuIBW7y+L8BcBdPtzl49jBolm08+S9vgIr8cf9MtONdU37Al+uOTVki2g==",
"dev": true "dev": true
}, },
"@ucap/protocol-service": { "@ucap/protocol-service": {

View File

@ -148,7 +148,7 @@
"@types/moment-timezone": "^0.5.12", "@types/moment-timezone": "^0.5.12",
"@types/node": "^12.12.30", "@types/node": "^12.12.30",
"@ucap/api": "~0.0.1", "@ucap/api": "~0.0.1",
"@ucap/api-common": "~0.0.1", "@ucap/api-common": "~0.0.5",
"@ucap/api-external": "~0.0.2", "@ucap/api-external": "~0.0.2",
"@ucap/api-message": "~0.0.1", "@ucap/api-message": "~0.0.1",
"@ucap/api-prompt": "~0.0.1", "@ucap/api-prompt": "~0.0.1",
@ -162,7 +162,7 @@
"@ucap/ng-api-message": "file:pack/ucap-ng-api-message-0.0.1.tgz", "@ucap/ng-api-message": "file:pack/ucap-ng-api-message-0.0.1.tgz",
"@ucap/ng-api-prompt": "file:pack/ucap-ng-api-prompt-0.0.1.tgz", "@ucap/ng-api-prompt": "file:pack/ucap-ng-api-prompt-0.0.1.tgz",
"@ucap/ng-api-public": "file:pack/ucap-ng-api-public-0.0.1.tgz", "@ucap/ng-api-public": "file:pack/ucap-ng-api-public-0.0.1.tgz",
"@ucap/ng-core": "file:pack/ucap-ng-core-0.0.1.tgz", "@ucap/ng-core": "file:pack/ucap-ng-core-0.0.7.tgz",
"@ucap/ng-i18n": "file:pack/ucap-ng-i18n-0.0.6.tgz", "@ucap/ng-i18n": "file:pack/ucap-ng-i18n-0.0.6.tgz",
"@ucap/ng-logger": "file:pack/ucap-ng-logger-0.0.2.tgz", "@ucap/ng-logger": "file:pack/ucap-ng-logger-0.0.2.tgz",
"@ucap/ng-native": "file:pack/ucap-ng-native-0.0.1.tgz", "@ucap/ng-native": "file:pack/ucap-ng-native-0.0.1.tgz",
@ -185,15 +185,15 @@
"@ucap/ng-protocol-sync": "file:pack/ucap-ng-protocol-sync-0.0.3.tgz", "@ucap/ng-protocol-sync": "file:pack/ucap-ng-protocol-sync-0.0.3.tgz",
"@ucap/ng-protocol-umg": "file:pack/ucap-ng-protocol-umg-0.0.3.tgz", "@ucap/ng-protocol-umg": "file:pack/ucap-ng-protocol-umg-0.0.3.tgz",
"@ucap/ng-store-authentication": "file:pack/ucap-ng-store-authentication-0.0.11.tgz", "@ucap/ng-store-authentication": "file:pack/ucap-ng-store-authentication-0.0.11.tgz",
"@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.13.tgz", "@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.16.tgz",
"@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.14.tgz", "@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.14.tgz",
"@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.8.tgz", "@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.8.tgz",
"@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.19.tgz", "@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.19.tgz",
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.24.tgz", "@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.25.tgz",
"@ucap/ng-ui-chat": "file:pack/ucap-ng-ui-chat-0.0.9.tgz", "@ucap/ng-ui-chat": "file:pack/ucap-ng-ui-chat-0.0.12.tgz",
"@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.33.tgz", "@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.33.tgz",
"@ucap/ng-ui-material": "file:pack/ucap-ng-ui-material-0.0.4.tgz", "@ucap/ng-ui-material": "file:pack/ucap-ng-ui-material-0.0.4.tgz",
"@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.55.tgz", "@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.83.tgz",
"@ucap/ng-ui-skin-default": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz", "@ucap/ng-ui-skin-default": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz",
"@ucap/ng-web-socket": "file:pack/ucap-ng-web-socket-0.0.2.tgz", "@ucap/ng-web-socket": "file:pack/ucap-ng-web-socket-0.0.2.tgz",
"@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.3.tgz", "@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.3.tgz",
@ -209,7 +209,7 @@
"@ucap/protocol-option": "~0.0.7", "@ucap/protocol-option": "~0.0.7",
"@ucap/protocol-ping": "~0.0.4", "@ucap/protocol-ping": "~0.0.4",
"@ucap/protocol-query": "~0.0.5", "@ucap/protocol-query": "~0.0.5",
"@ucap/protocol-room": "~0.0.5", "@ucap/protocol-room": "~0.0.6",
"@ucap/protocol-service": "~0.0.4", "@ucap/protocol-service": "~0.0.4",
"@ucap/protocol-status": "~0.0.5", "@ucap/protocol-status": "~0.0.5",
"@ucap/protocol-sync": "~0.0.4", "@ucap/protocol-sync": "~0.0.4",

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-core", "name": "@ucap/ng-core",
"version": "0.0.1", "version": "0.0.7",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -0,0 +1,24 @@
import { Params } from '@angular/router';
export class ParamsUtil {
static getParameter<T>(params: Params, key: string, defaultValue: T): T {
const v = params[key];
if (undefined === v) {
if (undefined !== defaultValue) {
return defaultValue;
}
return undefined;
}
switch (typeof defaultValue) {
case 'boolean':
return ((v as string).toLowerCase() === 'true' ? true : false) as any;
case 'number':
return Number(v) as any;
default:
break;
}
return v;
}
}

View File

@ -10,6 +10,7 @@ export interface StickerFilesInfo {
index: string; index: string;
path: string; path: string;
} }
export const KEY_STICKER_HISTORY = 'ucap::Sticker_History';
export const StickerMap: StickerInfo[] = [ export const StickerMap: StickerInfo[] = [
{ {
index: '00', index: '00',
@ -138,15 +139,18 @@ export const StickerMap: StickerInfo[] = [
} }
]; ];
const ActiveAndOrdering: string[] = ['00', '01', '02', '03', '04', '05']; export const ActiveAndOrdering: string[] = ['00', '01', '02', '03', '04', '05'];
export class StickerUtil { export class StickerUtil {
static getStickerInfoList(): StickerInfo[] { static getStickerInfoList(
stickerMap: StickerInfo[] = StickerMap,
activeAndOrdering: string[] = ActiveAndOrdering
): StickerInfo[] {
const rtnStickerList: StickerInfo[] = []; const rtnStickerList: StickerInfo[] = [];
ActiveAndOrdering.forEach(idx => { activeAndOrdering.forEach((idx) => {
const stickerInfos: StickerInfo[] = StickerMap.filter( const stickerInfos: StickerInfo[] = stickerMap.filter(
sticker => sticker.index === idx && sticker.useYn (sticker) => sticker.index === idx && sticker.useYn
); );
if (!!stickerInfos && stickerInfos.length > 0) { if (!!stickerInfos && stickerInfos.length > 0) {
@ -156,17 +160,4 @@ export class StickerUtil {
return rtnStickerList; return rtnStickerList;
} }
static getStickerInfoSub(index: string): StickerFilesInfo[] {
const stickerFilesList: StickerFilesInfo[] = [];
const stickerInfos: StickerInfo[] = StickerMap.filter(
sticker => sticker.index === index && sticker.useYn
);
if (!!stickerInfos && stickerInfos.length > 0) {
stickerFilesList.concat(stickerInfos[0].fileInfos);
}
return stickerFilesList;
}
} }

View File

@ -2,6 +2,7 @@
* Public API Surface of core * Public API Surface of core
*/ */
export * from './lib/utils/params.util';
export * from './lib/utils/sticker.util'; export * from './lib/utils/sticker.util';
export * from './lib/utils/string.util'; export * from './lib/utils/string.util';
export * from './lib/utils/window.util'; export * from './lib/utils/window.util';

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-store-chat", "name": "@ucap/ng-store-chat",
"version": "0.0.13", "version": "0.0.16",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -83,7 +83,22 @@ export const fileInfosFailure = createAction(
*/ */
export const addEvent = createAction( export const addEvent = createAction(
'[ucap::chat::chatting] addEvent', '[ucap::chat::chatting] addEvent',
props<{ req: FileInfoRequest }>() 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( export const read = createAction(
@ -105,3 +120,26 @@ export const readFailure = createAction(
'[ucap::chat::chatting] read Failure', '[ucap::chat::chatting] read Failure',
props<{ error: any }>() 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 }>()
);

View File

@ -5,13 +5,16 @@ import {
map, map,
catchError, catchError,
exhaustMap, exhaustMap,
concatMap,
withLatestFrom,
debounceTime debounceTime
} from 'rxjs/operators'; } from 'rxjs/operators';
import { Injectable, Inject } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Dictionary } from '@ngrx/entity';
import { EventProtocolService } from '@ucap/ng-protocol-event'; import { EventProtocolService } from '@ucap/ng-protocol-event';
import { RoomProtocolService } from '@ucap/ng-protocol-room'; import { RoomProtocolService } from '@ucap/ng-protocol-room';
@ -27,13 +30,24 @@ import {
readSuccess, readSuccess,
fileInfos, fileInfos,
fileInfosFailure, fileInfosFailure,
fileInfosSuccess fileInfosSuccess,
send,
sendSuccess,
sendFailure,
addEvent,
addEventSuccess
} from './actions'; } from './actions';
import { InfoRequest, ReadResponse, FileType } from '@ucap/protocol-event'; import {
InfoRequest,
ReadResponse,
FileType,
EventType
} from '@ucap/protocol-event';
import { ModuleConfig } from '../../config/module-config'; import { ModuleConfig } from '../../config/module-config';
import { _MODULE_CONFIG } from '../../config/token'; import { _MODULE_CONFIG } from '../../config/token';
import { Chatting } from './state';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -158,6 +172,89 @@ export class Effects {
{ dispatch: false } { 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;
console.log('sendSuccess', action);
this.store.dispatch(
addEvent({
roomId: res.roomId,
info: res.info,
SVC_TYPE: res.SVC_TYPE,
SSVC_TYPE: res.SSVC_TYPE
})
);
})
);
},
{ dispatch: false }
);
constructor( constructor(
private actions$: Actions, private actions$: Actions,
private store: Store<any>, private store: Store<any>,

View File

@ -15,7 +15,8 @@ import {
eventsFailure, eventsFailure,
readSuccess, readSuccess,
fileInfosSuccess, fileInfosSuccess,
fileInfosFailure fileInfosFailure,
addEvent
} from './actions'; } from './actions';
export const reducer = createReducer( export const reducer = createReducer(
@ -163,5 +164,29 @@ export const reducer = createReducer(
...state.chattings ...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;
}
}) })
); );

View File

@ -119,6 +119,20 @@ export const roomFailure = createAction(
'[ucap::chat::room] room Failure', '[ucap::chat::room] room Failure',
props<{ error: any }>() props<{ error: any }>()
); );
/**
* Success of rooms2 request
* RoomUserData is detail, short type both.
*/
export const room2Success = createAction(
'[ucap::chat::room] room2 Success',
props<{
roomInfo: RoomInfo;
roomUserInfo: {
userInfoShortList: UserInfoShort[];
userInfoList: RoomUserInfo[];
};
}>()
);
/** /**
* create room * create room

View File

@ -28,10 +28,10 @@ import {
} from '@ucap/protocol-room'; } from '@ucap/protocol-room';
import { RoomProtocolService } from '@ucap/ng-protocol-room'; import { RoomProtocolService } from '@ucap/ng-protocol-room';
import { SyncProtocolService } from '@ucap/ng-protocol-sync'; import { SyncProtocolService } from '@ucap/ng-protocol-sync';
import { LoginActions } from '@ucap/ng-store-authentication'; import { LoginActions } from '@ucap/ng-store-authentication';
import { addEventSuccess } from '../Chatting/actions';
import { RoomSelector } from '../state'; import { RoomSelector } from '../state';
@ -77,8 +77,11 @@ import {
delMulti, delMulti,
delMultiSuccess, delMultiSuccess,
delMultiFailure, delMultiFailure,
selectedRoom selectedRoom,
room2Success
} from './actions'; } from './actions';
import { Router } from '@angular/router';
import { LocaleCode } from '@ucap/core';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -100,8 +103,6 @@ export class Effects {
isDetail: false isDetail: false
}; };
console.log(req);
// retrieve room info // retrieve room info
this.store.dispatch(room({ req })); this.store.dispatch(room({ req }));
@ -151,13 +152,25 @@ export class Effects {
room$ = createEffect(() => { room$ = createEffect(() => {
return this.actions$.pipe( return this.actions$.pipe(
ofType(room), ofType(room),
map((action) => action.req), switchMap((action) => {
switchMap((req) => { const req = action.req;
return this.roomProtocolService.info(req).pipe( // // 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) => map((res) =>
roomSuccess({ room2Success({
roomInfo: res.roomInfo, roomInfo: res.roomInfo,
userInfoList: res.userInfoList roomUserInfo: res.roomUserInfo
}) })
), ),
catchError((error) => of(roomFailure({ error }))) catchError((error) => of(roomFailure({ error })))
@ -166,19 +179,40 @@ export class Effects {
); );
}); });
create$ = createEffect(() => create$ = createEffect(
this.actions$.pipe( () => {
return this.actions$.pipe(
ofType(create), ofType(create),
map((action) => action.req), map((action) => action.req),
exhaustMap((req) => { exhaustMap((req) => {
return this.roomProtocolService.open(req).pipe( return this.roomProtocolService.open(req).pipe(
map((res: CreateResponse) => { map((res: CreateResponse) => {
return createSuccess({ res }); console.log('CreateResponse', res);
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 }))) catchError((error) => of(createFailure({ error })))
); );
}) })
) );
},
{ dispatch: false }
); );
createTimer$ = createEffect(() => createTimer$ = createEffect(() =>
@ -390,9 +424,30 @@ export class Effects {
); );
}); });
/*******************************************************************
* [Chatting Action watching.]
*******************************************************************/
addEventSuccess$ = createEffect(
() => {
return this.actions$.pipe(
ofType(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: LocaleCode.Korean }));
}
})
);
},
{ dispatch: false }
);
constructor( constructor(
private actions$: Actions, private actions$: Actions,
private store: Store<any>, private store: Store<any>,
private router: Router,
private syncProtocolService: SyncProtocolService, private syncProtocolService: SyncProtocolService,
private roomProtocolService: RoomProtocolService private roomProtocolService: RoomProtocolService
) {} ) {}

View File

@ -24,9 +24,12 @@ import {
delSuccess, delSuccess,
rooms2Success, rooms2Success,
delMultiSuccess, delMultiSuccess,
updateSuccess updateSuccess,
room2Success,
createSuccess
} from './actions'; } from './actions';
import { userInfo } from 'os'; import { Info, EventJson } from '@ucap/protocol-event';
import { ChatUtil } from '../../utils/chat.util';
export const reducer = createReducer( export const reducer = createReducer(
initialState, initialState,
@ -136,6 +139,49 @@ export const reducer = createReducer(
}; };
}), }),
on(room2Success, (state, action) => {
const roomUserMapList: RoomUserMap[] = [];
const roomUserShortMapList: RoomUserShortMap[] = [];
if (
!!action.roomUserInfo.userInfoList &&
0 < action.roomUserInfo.userInfoList.length
) {
roomUserMapList.push({
roomId: action.roomInfo.roomId,
userInfos: action.roomUserInfo.userInfoList
});
}
if (
!!action.roomUserInfo.userInfoShortList &&
0 < action.roomUserInfo.userInfoShortList.length
) {
roomUserShortMapList.push({
roomId: action.roomInfo.roomId,
userInfos: action.roomUserInfo.userInfoShortList
});
}
return {
...state,
rooms: adapterRoom.upsertOne(action.roomInfo, {
...state.rooms
}),
roomUsers:
0 < roomUserMapList.length
? adapterRoomUser.upsertMany(roomUserMapList, {
...state.roomUsers
})
: state.roomUsers,
roomUsersShort:
0 < roomUserShortMapList.length
? adapterRoomUserShort.upsertMany(roomUserShortMapList, {
...state.roomUsersShort
})
: state.roomUsersShort
};
}),
on(excludeUserSuccess, (state, action) => { on(excludeUserSuccess, (state, action) => {
const roomId = action.roomId; const roomId = action.roomId;
const roomUserMap = state.roomUsers.entities[roomId]; const roomUserMap = state.roomUsers.entities[roomId];
@ -191,6 +237,41 @@ export const reducer = createReducer(
}; };
}), }),
on(createSuccess, (state, action) => {
const standby = state.standbyRooms;
const curRoomId = action.res.roomId;
if (standby.findIndex((roomId) => roomId === curRoomId) > -1) {
// Exist.
return state;
} else {
// Not Exist.
return {
...state,
standbyRooms: [...state.standbyRooms, curRoomId]
};
}
}),
on(delSuccess, (state, action) => {
const roomId = action.res.roomId;
const room = state.rooms.entities[roomId];
if (!room) {
return state;
}
return {
...state,
rooms: adapterRoom.removeOne(roomId, { ...state.rooms }),
roomUsers: adapterRoomUser.removeOne(roomId, {
...state.roomUsers
}),
roomUsersShort: adapterRoomUserShort.removeOne(roomId, {
...state.roomUsersShort
})
};
}),
on(updateSuccess, (state, action) => { on(updateSuccess, (state, action) => {
const roomInfo = { const roomInfo = {
...state.rooms.entities[action.res.roomId], ...state.rooms.entities[action.res.roomId],
@ -243,9 +324,9 @@ export const reducer = createReducer(
}; };
}), }),
/** /*******************************************************************
* [Chatting Action watching.] * [Chatting Action watching.]
*/ *******************************************************************/
on(chattingActions.readSuccess, (state, action) => { on(chattingActions.readSuccess, (state, action) => {
const roomId = action.roomId; const roomId = action.roomId;
const trgtUserSeq = action.SENDER_SEQ; const trgtUserSeq = action.SENDER_SEQ;
@ -279,7 +360,10 @@ export const reducer = createReducer(
userInfos: state.roomUsersShort.entities[roomId].userInfos.map( userInfos: state.roomUsersShort.entities[roomId].userInfos.map(
(roomUserInfo) => { (roomUserInfo) => {
if (roomUserInfo.seq === Number(trgtUserSeq)) { if (roomUserInfo.seq === Number(trgtUserSeq)) {
return { ...roomUserInfo, lastReadEventSeq: action.lastReadSeq }; return {
...roomUserInfo,
lastReadEventSeq: action.lastReadSeq
};
} else { } else {
return roomUserInfo; return roomUserInfo;
} }
@ -301,5 +385,44 @@ export const reducer = createReducer(
}) })
: state.roomUsersShort : state.roomUsersShort
}; };
}),
on(chattingActions.addEventSuccess, (state, action) => {
const roomId = action.roomId;
const info: Info<EventJson> = action.info;
if (!roomId || !info) {
return state;
}
const currentRoomInfo = state.rooms.entities[roomId];
const fixedStandByRooms = state.standbyRooms.filter(
(standbyRoomId) => standbyRoomId !== roomId
);
if (!!currentRoomInfo) {
const finalEventMessage:
| string
| null = ChatUtil.convertFinalEventMessage(
action.info.type,
action.info.sentMessageJson || action.info.sentMessage
);
const roomInfo = {
...currentRoomInfo,
finalEventType: info.type,
finalEventDate: info.sendDate,
finalEventMessage,
finalEventSeq: info.seq
} as RoomInfo;
return {
...state,
rooms: adapterRoom.upsertOne(roomInfo, { ...state.rooms }),
standbyRooms: fixedStandByRooms
};
} else {
return state;
}
}) })
); );

View File

@ -47,6 +47,7 @@ export interface State {
rooms: RoomState; rooms: RoomState;
roomUsers: RoomUserState; roomUsers: RoomUserState;
roomUsersShort: RoomUserShortState; roomUsersShort: RoomUserShortState;
standbyRooms: string[];
} }
const roomInitialState: RoomState = adapterRoom.getInitialState({ const roomInitialState: RoomState = adapterRoom.getInitialState({
@ -60,7 +61,8 @@ const roomUserShortInitialState: RoomUserShortState = adapterRoomUserShort.getIn
export const initialState: State = { export const initialState: State = {
rooms: roomInitialState, rooms: roomInitialState,
roomUsers: roomUserInitialState, roomUsers: roomUserInitialState,
roomUsersShort: roomUserShortInitialState roomUsersShort: roomUserShortInitialState,
standbyRooms: []
}; };
const { const {
@ -128,6 +130,10 @@ export function selectors<S>(selector: Selector<any, State>) {
); );
} }
), ),
standbyRooms: createSelector(
selector,
(state: State) => state.standbyRooms
),
unreadTotal: createSelector( unreadTotal: createSelector(
selectRooms, selectRooms,
selectAllForRoom, selectAllForRoom,

View File

@ -0,0 +1,138 @@
import {
EventJson,
EventType,
FileEventJson,
FileType,
VideoConferenceEventJson,
VideoConferenceContentsType,
MassTextEventJson,
TranslationEventJson,
MassTranslationEventJson,
PlanEventJson,
PlanContentType
} from '@ucap/protocol-event';
export class ChatUtil {
public static convertFinalEventMessage(
eventType: EventType,
finalEventMessage: EventJson
): string | null {
let eventMessage: string = null;
switch (eventType) {
case EventType.Join:
case EventType.Exit:
case EventType.ForcedExit:
case EventType.RenameRoom:
case EventType.NotificationForTimerRoom:
case EventType.GuideForRoomTimerChanged:
{
/**
* .
* @description Edit with ui-chat > messages.component.ts
*/
}
break;
case EventType.Sticker:
{
eventMessage = '스티커';
}
break;
case EventType.File:
{
const m = finalEventMessage as FileEventJson;
if (FileType.Image === m.fileType) {
eventMessage = '이미지';
} else {
eventMessage = '첨부파일';
}
}
break;
case EventType.VideoConference:
{
const m = finalEventMessage as VideoConferenceEventJson;
switch (m.contents) {
case VideoConferenceContentsType.Now:
eventMessage = '화상회의가 개설되었습니다.';
break;
case VideoConferenceContentsType.New:
eventMessage = '화상회의가 등록되었습니다.';
break;
case VideoConferenceContentsType.Update:
eventMessage = '화상회의가 수정되었습니다.';
break;
case VideoConferenceContentsType.Delete:
eventMessage = '화상회의가 취소되었습니다.';
break;
default:
eventMessage = '화상회의';
break;
}
}
break;
case EventType.MassText:
{
const m = finalEventMessage as MassTextEventJson;
eventMessage = m.content;
}
break;
case EventType.Translation:
{
const m = finalEventMessage as TranslationEventJson;
eventMessage = m.original;
}
break;
case EventType.MassTranslation:
{
const m = finalEventMessage as MassTranslationEventJson;
eventMessage = m.original;
}
break;
case EventType.Plan:
{
const m = finalEventMessage as PlanEventJson;
switch (m.contents) {
case PlanContentType.New:
eventMessage = '새로운 일정이 등록되었습니다.';
break;
case PlanContentType.Update:
eventMessage = '일정이 수정되었습니다.';
break;
case PlanContentType.Delete:
eventMessage = '일정이 취소되었습니다.';
break;
default:
eventMessage = '일정이 업데이트 되었습니다.';
break;
}
}
break;
case EventType.NotificationForiOSCapture:
{
const m = finalEventMessage as string;
eventMessage = `${m}님이 대화내용을 캡쳐하였습니다.`;
}
break;
case EventType.AllimElephant: // [daesang] 알림
{
eventMessage = '코끼리 알림';
}
break;
case EventType.AllimTms: // [daesang] 알림
{
eventMessage = 'TMS 알림';
}
break;
default:
{
const m = finalEventMessage as string;
eventMessage = m;
}
break;
}
return eventMessage;
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-authentication", "name": "@ucap/ng-ui-authentication",
"version": "0.0.24", "version": "0.0.25",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -6,7 +6,7 @@
class="ucap-authentication-login-input-field" class="ucap-authentication-login-input-field"
[style.display]="!!fixedCompanyCode ? 'none' : 'block'" [style.display]="!!fixedCompanyCode ? 'none' : 'block'"
> >
<mat-form-field> <mat-form-field color="accent">
<mat-label>{{ 'login.fields.company' | ucapI18n }}</mat-label> <mat-label>{{ 'login.fields.company' | ucapI18n }}</mat-label>
<mat-select <mat-select
[formControl]="companyCodeFormControl" [formControl]="companyCodeFormControl"
@ -26,16 +26,27 @@
<div class="ucap-authentication-login-input-field"> <div class="ucap-authentication-login-input-field">
<!--<span class="input-icon"><i class="mid mdi-account-tie-outline"></i></span>--> <!--<span class="input-icon"><i class="mid mdi-account-tie-outline"></i></span>-->
<span class="ucap-authentication-login-input-icon"> <span class="ucap-authentication-login-input-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 22"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M0 0H24V24H0z" style="fill: none;" />
<g transform="translate(.118 -1)">
<path <path
d="M14.39,11.88a5.7,5.7,0,1,0-4.78,0A10.41,10.41,0,0,0,1.59,22v1H22.41V22A10.41,10.41,0,0,0,14.39,11.88Z" d="M58.571 48.146a8.3 8.3 0 1 0-16.571 0"
transform="translate(-1.59 -1)" transform="translate(-38.403 -25.592)"
style="fill: #fff;" style="
/> stroke-miterlimit: 10;
<path stroke: #4c4c4c;
d="M14.39,11.88a5.7,5.7,0,1,0-4.78,0A10.41,10.41,0,0,0,1.59,22v1H22.41V22A10.41,10.41,0,0,0,14.39,11.88ZM8.3,6.7A3.71,3.71,0,1,1,12,10.41,3.7,3.7,0,0,1,8.3,6.7ZM3.65,21a8.42,8.42,0,0,1,7.58-7.37L10,16.58l2,2,2-2-1.23-2.95A8.42,8.42,0,0,1,20.35,21Z" stroke-width: 2px;
transform="translate(-1.59 -1)" fill: none;
"
/> />
<g
transform="translate(7.037 3.808)"
style="stroke: #4c4c4c; stroke-width: 2px; fill: none;"
>
<circle cx="4.5" cy="4.5" r="4.5" style="stroke: none;" />
<circle cx="4.5" cy="4.5" r="3.5" style="fill: none;" />
</g>
</g>
</svg> </svg>
</span> </span>
<mat-form-field> <mat-form-field>
@ -47,30 +58,41 @@
<div class="ucap-authentication-login-input-field"> <div class="ucap-authentication-login-input-field">
<!--<span class="input-icon"><i class="mid mdi-lock-outline"></i></span>--> <!--<span class="input-icon"><i class="mid mdi-lock-outline"></i></span>-->
<span class="ucap-authentication-login-input-icon"> <span class="ucap-authentication-login-input-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 22.07"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>login-lock</title> <path d="M0 0H24V24H0z" style="fill: none;" />
<path <g
d="M19,10H18V7A6,6,0,0,0,6,7v3H5a3,3,0,0,0-3,3v7a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V13A3,3,0,0,0,19,10Z" transform="translate(2 9)"
transform="translate(-2 -0.93)" style="stroke-width: 2px; stroke: #4c4c4c; fill: #fff;"
style="fill: #fff;" >
/> <rect width="20" height="14" style="stroke: none;" rx="2" />
<rect x="9.08" y="12.94" width="1.84" height="3.72" />
<path
d="M19,22.93H5a3,3,0,0,1-3-3v-7a3,3,0,0,1,3-3H19a3,3,0,0,1,3,3v7A3,3,0,0,1,19,22.93Zm-14-11a1,1,0,0,0-1,1v7a1,1,0,0,0,1,1H19a1,1,0,0,0,1-1v-7a1,1,0,0,0-1-1Z"
transform="translate(-2 -0.93)"
/>
<path
d="M17,11.93a1,1,0,0,1-1-1v-4a4,4,0,0,0-8,0v4a1,1,0,0,1-2,0v-4a6,6,0,0,1,12,0v4A1,1,0,0,1,17,11.93Z"
transform="translate(-2 -0.93)"
/>
<rect <rect
x="9.58" width="18"
y="13.36" height="12"
width="0.84" x="1"
height="2.72" y="1"
style="fill: #fff;" style="fill: none;"
rx="1"
/> />
<rect x="9.08" y="12.86" width="1.84" height="3.72" /> </g>
<g style="fill: #fff;">
<path
d="M11 8H1V6c0-2.757 2.243-5 5-5s5 2.243 5 5v2z"
style="stroke: none;"
transform="translate(6 2)"
/>
<path
d="M10 7V6c0-2.205-1.794-4-4-4-2.205 0-4 1.795-4 4v1h8m1.917 2H.083C.028 8.67 0 8.332 0 8V6c0-3.308 2.692-6 6-6s6 2.692 6 6v2c0 .335-.028.67-.083.998V9z"
style="stroke: none; fill: #4c4c4c;"
transform="translate(6 2)"
/>
</g>
<g
transform="translate(10 14)"
style="fill: #4c4c4c; stroke: #4c4c4c;"
>
<circle cx="2" cy="2" r="2" style="stroke: none;" />
<circle cx="2" cy="2" r="1.5" style="fill: none;" />
</g>
</svg> </svg>
</span> </span>
<mat-form-field> <mat-form-field>

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-chat", "name": "@ucap/ng-ui-chat",
"version": "0.0.9", "version": "0.0.12",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -107,7 +107,7 @@ export class ChatUiRootModule {}
providers: [ providers: [
{ {
provide: UCAP_I18N_NAMESPACE, provide: UCAP_I18N_NAMESPACE,
useValue: ['chat'] useValue: ['chat', 'common']
} }
] ]
}) })

View File

@ -5,6 +5,7 @@
> >
<ucap-chat-message-box-attach-file <ucap-chat-message-box-attach-file
*ngSwitchCase="FileType.File" *ngSwitchCase="FileType.File"
[fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(openViewer)="onClickFileViewer(fileInfo)" (openViewer)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
@ -13,6 +14,7 @@
<ucap-chat-message-box-attach-file <ucap-chat-message-box-attach-file
*ngSwitchCase="FileType.Sound" *ngSwitchCase="FileType.Sound"
[fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(openViewer)="onClickFileViewer(fileInfo)" (openViewer)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
@ -21,6 +23,7 @@
<ucap-chat-message-box-image <ucap-chat-message-box-image
*ngSwitchCase="FileType.Image" *ngSwitchCase="FileType.Image"
[fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(openViewer)="onClickFileViewer(fileInfo)" (openViewer)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
@ -28,14 +31,14 @@
<ucap-chat-message-box-video <ucap-chat-message-box-video
*ngSwitchCase="FileType.Video" *ngSwitchCase="FileType.Video"
[fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(openViewer)="onClickFileViewer(fileInfo)" (openViewer)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
></ucap-chat-message-box-video> ></ucap-chat-message-box-video>
<!--
<ucap-chat-message-box-text <ucap-chat-message-box-text
*ngSwitchDefault *ngSwitchDefault
[message]="message" [message]="message"
></ucap-chat-message-box-text> --> ></ucap-chat-message-box-text>
</ng-container> </ng-container>

View File

@ -1,88 +1,53 @@
<div class="ucap-chat-message-box-information"> <div class="ucap-chat-message-box-information">
<ng-container [ngSwitch]="message.type"> <ng-container [ngSwitch]="message.type">
<ng-container *ngSwitchCase="EventType.Join"> <ng-container *ngSwitchCase="EventType.Join">
{{ getI18nForSir(getOwnerForJoinEventJson()) }}이 {{
{{ getI18nForSir(getInviterForJoinEventJson()) }}을 초대했습니다. 'event.inviteToRoomWith'
<!-- {{ | ucapI18n
'chat.event.inviteToRoomWith'
| translate
: { : {
owner: getI18nForSir(message.sentMessageJson.owner), owner: getOwnerForJoinEventJson(),
inviter: getI18nForSir(message.sentMessageJson.inviter) inviter: getInviterForJoinEventJson()
} }
}} --> }}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.Exit"> <ng-container *ngSwitchCase="EventType.Exit">
{{ message.sentMessage }}님이 퇴장하셨습니다. {{ 'event.exitFromRoomWith' | ucapI18n: { exitor: message.sentMessage } }}
<!-- {{
'chat.event.exitFromRoomWith'
| translate: { exitor: message.sentMessage }
}} -->
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.ForcedExit"> <ng-container *ngSwitchCase="EventType.ForcedExit">
{{ message.exitForcingRequestUserName }}님이 {{ message.sentMessage }}님을 {{
퇴장 시키셨습니다. 'event.ejectedFromRoomWith'
<!-- {{ | ucapI18n
'chat.event.ejectedFromRoomWith'
| translate
: { : {
requester: message.exitForcingRequestUserName, requester: message.exitForcingRequestUserName,
ejected: message.sentMessage ejected: message.sentMessage
} }
}} --> }}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.RenameRoom"> <ng-container *ngSwitchCase="EventType.RenameRoom">
{{ getRequesterForRenameRoom() }}님이 대화방명을 '{{ {{
getRoomNameForRenameRoom() 'event.renamedRoomWith'
}}'으로 변경하셨습니다. | ucapI18n
<!-- {{
'chat.event.renamedRoomWith'
| translate
: { : {
requester: message.sentMessageJson.requester, requester: getRequesterForRenameRoom(),
roomName: message.sentMessageJson.roomName roomName: getRoomNameForRenameRoom()
} }
}} --> }}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.NotificationForTimerRoom"> <ng-container *ngSwitchCase="EventType.NotificationForTimerRoom">
{{ message.sentMessage }} {{ message.sentMessage }}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.GuideForRoomTimerChanged"> <ng-container *ngSwitchCase="EventType.GuideForRoomTimerChanged">
{{ senderName }}님이 타이머를 설정하였습니다. ({{ {{
getCalcTimerForGuideForRoomTimerChanged() 'event.setTimerWith'
}}) | ucapI18n
<!-- {{
'chat.event.setTimerWith'
| translate
: { : {
requester: senderName, requester: senderName,
timer: getCalcTimer(message.sentMessageJson.time * 1000) timer: getCalcTimerForGuideForRoomTimerChanged()
} }
}} --> }}
</ng-container> </ng-container>
<ng-container *ngSwitchCase="EventType.NotificationForiOSCapture"> <ng-container *ngSwitchCase="EventType.NotificationForiOSCapture">
{{ message.sentMessage }}님이 대화내용을 캡쳐하였습니다. {{ 'event.iosCapture' | ucapI18n: { requester: message.sentMessage } }}
<!-- {{
'chat.event.iosCapture'
| translate
: {
requester: message.sentMessage
}
}} -->
</ng-container> </ng-container>
</ng-container> </ng-container>
</div> </div>
<!-- <div class="ucap-chat-info-event">
<div class="info bg-primary-chat">
<strong class="text-warn-chat"
>김나영, 김준혁,강은정,나정미,박미영,박정은, 박훈, 문영준, 이지은, 이진현,
이현준, 임영찬, 임찬우, 정민우,정현욱, 최남진, 최영은, 최진우, 한고은,
한정후, 한지민</strong
>님이 초대되었습니다.
</div>
<div class="chat-event others bg-accent-chat">
자동 삭제 타이머가 10분으로 변경되었습니다.
</div>
</div> -->

View File

@ -7,7 +7,7 @@ import {
GuideForRoomTimerChangedEventJson, GuideForRoomTimerChangedEventJson,
JoinEventJson JoinEventJson
} from '@ucap/protocol-event'; } from '@ucap/protocol-event';
import { TranslateService } from '@ucap/ng-ui-organization'; import { I18nService } from '@ucap/ng-i18n';
@Component({ @Component({
selector: 'ucap-chat-message-box-information', selector: 'ucap-chat-message-box-information',
@ -23,105 +23,64 @@ export class InformationComponent implements OnInit {
EventType = EventType; EventType = EventType;
constructor(private translateService: TranslateService) {} constructor(private i18nService: I18nService) {}
ngOnInit() {} ngOnInit() {}
getI18nForSir(names: string | string[]) { protected getI18nForSir(names: string | string[]) {
if (Array.isArray(names)) { if (Array.isArray(names)) {
const inviter: string[] = []; return names.join(',');
// names.forEach((userName, idx) => {
// inviter.push(
// this.translateService.instant('common.messages.sirWith', {
// sir: userName
// })
// );
// });
names.forEach((userName, idx) => {
inviter.push(userName);
});
return inviter.join(',');
} else { } else {
// return this.translateService.instant('common.messages.sirWith', {
// sir: names
// });
return names; return names;
} }
} }
getOwnerForJoinEventJson() { getOwnerForJoinEventJson() {
return (this.message.sentMessageJson as JoinEventJson).owner; return this.getI18nForSir(
(this.message.sentMessageJson as JoinEventJson).owner
);
} }
getInviterForJoinEventJson() { getInviterForJoinEventJson() {
return (this.message.sentMessageJson as JoinEventJson).inviter; return this.getI18nForSir(
(this.message.sentMessageJson as JoinEventJson).inviter
);
} }
getCalcTimerForGuideForRoomTimerChanged() { getCalcTimerForGuideForRoomTimerChanged() {
const millisec = const millisec =
Number( Number(
(this.message.sentMessageJson as GuideForRoomTimerChangedEventJson).time (this.message.sentMessageJson as GuideForRoomTimerChangedEventJson).time
) * 1000; ) * 1000;
// const langs = this.translateService.instant([ const langs = this.i18nService.t([
// 'common.units.hourFrom', 'common.units.hourFrom',
// 'common.units.minute', 'common.units.minute',
// 'common.units.second' 'common.units.second'
// ]); ]);
// switch (millisec) {
// case 5000:
// return `5 ${langs['common.units.second']}`;
// case 10000:
// return `10 ${langs['common.units.second']}`;
// case 30000:
// return `30 ${langs['common.units.second']}`;
// case 60000:
// return `1 ${langs['common.units.minute']}`;
// case 300000:
// return `5 ${langs['common.units.minute']}`;
// case 600000:
// return `10 ${langs['common.units.minute']}`;
// case 1800000:
// return `30 ${langs['common.units.minute']}`;
// case 3600000:
// return `1 ${langs['common.units.hourFrom']}`;
// case 21600000:
// return `6 ${langs['common.units.hourFrom']}`;
// case 43200000:
// return `12 ${langs['common.units.hourFrom']}`;
// case 86400000:
// return `24 ${langs['common.units.hourFrom']}`;
// default:
// return `0 ${langs['common.units.second']}`;
// }
const langs = ['초', '분', '시간'];
switch (millisec) { switch (millisec) {
case 5000: case 5000:
return `5 ${langs[0]}`; return `5 ${langs['common.units.second']}`;
case 10000: case 10000:
return `10 ${langs[0]}`; return `10 ${langs['common.units.second']}`;
case 30000: case 30000:
return `30 ${langs[0]}`; return `30 ${langs['common.units.second']}`;
case 60000: case 60000:
return `1 ${langs[1]}`; return `1 ${langs['common.units.minute']}`;
case 300000: case 300000:
return `5 ${langs[1]}`; return `5 ${langs['common.units.minute']}`;
case 600000: case 600000:
return `10 ${langs[1]}`; return `10 ${langs['common.units.minute']}`;
case 1800000: case 1800000:
return `30 ${langs[1]}`; return `30 ${langs['common.units.minute']}`;
case 3600000: case 3600000:
return `1 ${langs[2]}`; return `1 ${langs['common.units.hourFrom']}`;
case 21600000: case 21600000:
return `6 ${langs[2]}`; return `6 ${langs['common.units.hourFrom']}`;
case 43200000: case 43200000:
return `12 ${langs[2]}`; return `12 ${langs['common.units.hourFrom']}`;
case 86400000: case 86400000:
return `24 ${langs[2]}`; return `24 ${langs['common.units.hourFrom']}`;
default: default:
return `0 ${langs[0]}`; return `0 ${langs['common.units.second']}`;
} }
} }

View File

@ -1,7 +1,10 @@
<div class="ucap-chat-message-box-sticker"> <div class="ucap-chat-message-box-sticker">
<ul> <ul>
<li *ngIf="stickerUrl"> <li>
<img [src]="stickerUrl" /> <ng-content
select="[ucapUiChatStickerComponent='stickerImage']"
></ng-content>
<!-- <img [src]="stickerUrl" /> -->
</li> </li>
<li <li
*ngIf="contents" *ngIf="contents"

View File

@ -18,19 +18,23 @@ export class StickerComponent implements OnInit, AfterViewInit {
@Input() @Input()
message: Info<StickerEventJson>; message: Info<StickerEventJson>;
@Input() // @Input()
stickerUrl?: string; // stickerImageRoot: string;
@Input() // @Input()
stickerDefaultImagePath: string; // stickerDefaultImage: string;
@Output() @Output()
openLink = new EventEmitter<string>(); openLink = new EventEmitter<string>();
// stickerUrl?: string;
contents: string; contents: string;
constructor(private elementRef: ElementRef) {} constructor(private elementRef: ElementRef) {}
ngOnInit() { ngOnInit() {
// if (!!this.message.sentMessageJson.file) {
// this.stickerUrl = `assets/sticker/sticker_s_${this.message.sentMessageJson.file}.png`;
// }
if (!!this.message.sentMessageJson?.chat) { if (!!this.message.sentMessageJson?.chat) {
this.contents = this.message.sentMessageJson.chat; this.contents = this.message.sentMessageJson.chat;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@ucap/ng-ui-organization", "name": "@ucap/ng-ui-organization",
"version": "0.0.55", "version": "0.0.83",
"publishConfig": { "publishConfig": {
"registry": "https://nexus.loafle.net/repository/npm-ucap/" "registry": "https://nexus.loafle.net/repository/npm-ucap/"
}, },

View File

@ -1,5 +1,8 @@
@import '~@ucap/ng-ui-material/material'; @import '~@ucap/ng-ui-material/material';
/// var
/// --ucap-organization-profile-list-item-01-size: 70px
.ucap-organization-profile-list-item-01-container { .ucap-organization-profile-list-item-01-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -23,6 +26,11 @@
flex-grow: 1.07; flex-grow: 1.07;
padding-left: 17px; padding-left: 17px;
.profile-image {
width: var(--ucap-organization-profile-list-item-01-size);
height: var(--ucap-organization-profile-list-item-01-size);
}
.user-info { .user-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -37,7 +45,7 @@
height: 22px; height: 22px;
.user-name { .user-name {
// @include ellipsis-column(1); @include ellipsis-column(1);
height: 22px; height: 22px;
font: { font: {
size: 14px; size: 14px;
@ -49,7 +57,7 @@
} }
.user-grade { .user-grade {
// @include ellipsis(1); @include ellipsis(1);
align-self: stretch; align-self: stretch;
font: { font: {
size: 13px; size: 13px;
@ -62,7 +70,7 @@
} }
.dept-name { .dept-name {
// @include ellipsis(1); @include ellipsis(1);
font: { font: {
size: 12px; size: 12px;
} }

View File

@ -36,7 +36,7 @@ export class ProfileListItem01Component implements OnInit, OnDestroy {
@Output() @Output()
changeCheck = new EventEmitter<{ changeCheck = new EventEmitter<{
isChecked: boolean; checked: boolean;
userInfo: UserInfoSS; userInfo: UserInfoSS;
}>(); }>();
@ -62,9 +62,9 @@ export class ProfileListItem01Component implements OnInit, OnDestroy {
} }
} }
onChangeCheck(isChecked: boolean): void { onChangeCheck(checked: boolean): void {
this.changeCheck.emit({ this.changeCheck.emit({
isChecked, checked,
userInfo: this.userInfo userInfo: this.userInfo
}); });
} }

View File

@ -0,0 +1,41 @@
<div class="ucap-organization-profile-list-item-01-container">
<div class="user-profile-info">
<div class="profile-image">
<ng-content
select="[ucapOrganizationProfileListItem01='profileImage']"
></ng-content>
</div>
<div class="user-info">
<div class="user-n-g">
<div class="user-name">
{{ userInfo | ucapOrganizationTranslate: 'name' }}
</div>
<div class="user-grade">
{{ userInfo | ucapOrganizationTranslate: 'grade' }}
</div>
</div>
<div class="dept-name">
{{ userInfo | ucapOrganizationTranslate: 'deptName' }}
</div>
</div>
</div>
<div class="user-info">
<ng-content
class="user-info-content"
select="[appProfileListItemUserInfo='info']"
></ng-content>
</div>
<div class="user-action">
<ng-content
class="user-action-content"
select="[appProfileListItemUserAction='action']"
></ng-content>
</div>
<div *ngIf="checkable">
<mat-checkbox
#checkbox
[checked]="checked"
(change)="onChangeCheck(checkbox.checked)"
></mat-checkbox>
</div>
</div>

View File

@ -0,0 +1,113 @@
@import '~@ucap/ng-ui-material/material';
/// var
/// --ucap-organization-profile-list-item-01-size: 70px
.ucap-organization-profile-list-item-01-container {
width: 100%;
height: 100%;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0;
height: 70px;
align-items: center;
&.line-top {
border-top: 1px solid #cccccc;
}
&.line-bottom {
border-bottom: 1px solid #cccccc;
}
.user-profile-info {
display: inline-flex;
flex-direction: row;
flex-grow: 1.07;
padding-left: 17px;
.profile-image {
width: var(--ucap-organization-profile-list-item-01-size);
height: var(--ucap-organization-profile-list-item-01-size);
}
.user-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-left: 16px;
.user-n-g {
display: flex;
flex-flow: row-reverse nowrap;
align-items: flex-end;
height: 22px;
.user-name {
@include ellipsis-column(1);
height: 22px;
font: {
size: 14px;
weight: 600;
}
color: #212121;
order: 1;
-ms-flex-order: 1;
}
.user-grade {
@include ellipsis(1);
align-self: stretch;
font: {
size: 13px;
}
color: #707070;
margin-left: 4px;
order: 0;
-ms-flex-order: 0;
}
}
.dept-name {
@include ellipsis(1);
font: {
size: 12px;
}
color: #666666;
line-height: 16px;
}
}
}
.company-info {
flex-grow: 1.4;
padding-left: 16px;
position: relative;
font: {
size: 14px;
}
color: #333333;
&:before {
content: '';
width: 1px;
height: 36px;
position: absolute;
left: 0;
top: calc(50% - 18px);
border-left: 1px solid #cccccc;
}
}
.contact {
flex-grow: 1;
font: {
size: 14px;
}
color: #333333;
}
.chk-box {
flex: 0 0 56px;
display: flex;
justify-content: center;
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ProfileListItem01Component } from './profile-list-item-01.component';
describe('ucap::ui-organization::ProfileListItem01Component', () => {
let component: ProfileListItem01Component;
let fixture: ComponentFixture<ProfileListItem01Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProfileListItem01Component]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProfileListItem01Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,68 @@
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 { UserInfoF } from '@ucap/protocol-query';
import { OrganizationUiModule } from '../organization-ui.module';
import { ProfileListItem02Component } from './profile-list-item-02.component';
import { RoleCode } from '@ucap/protocol-authentication';
import { CallMode, EmployeeType } from '@ucap/core';
const userInfo: UserInfoF = {
seq: 1234,
name: '사용자1',
profileImageFile: '사진파일',
grade: '직급',
intro: '업무소개',
companyCode: '기관코드',
hpNumber: '핸드폰번호',
lineNumber: '내선번호',
email: '이메일',
isMobile: true,
isBuddy: true,
isFavorit: true,
/** */
deptName: '부서명',
/** */
order: '조회순서',
isActive: true,
roleCd: RoleCode.Self,
employeeNum: 'A34567',
madn: 'MADN',
hardSadn: 'HARDPHONE_SADN',
fmcSadn: 'FMC_SADN',
nickName: 'nickName',
nameEn: '사용자명(영어)',
nameCn: '사용자명(중국어)',
gradeEn: '직급(영어)',
gradeCn: '직급(중국어)',
deptNameEn: '부서명(영어)',
deptNameCn: '부서명(중국어)',
isPrivacyAgree: true,
isValidLogin: true,
employeeType: EmployeeType.Regular
};
export default {
title: 'ui-organization::ProfileListItem02Component',
component: ProfileListItem02Component,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, OrganizationUiModule],
providers: []
})
]
};
export const Default = () => ({
component: ProfileListItem02Component,
props: {
userInfo
}
});

View File

@ -0,0 +1,24 @@
@import '~@ucap/ng-ui-material/material';
@mixin ucap-organization-profile-list-item-01-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-organization-profile-list-item-01-container {
border-color: mat-color($foreground, secondary-text);
}
}
@mixin ucap-organization-profile-list-item-01-typography($config) {
.ucap-organization-profile-list-item-01-container {
.searchword-container {
.form-field-input-searchword {
font-family: mat-font-family($config);
}
}
}
}

View File

@ -0,0 +1,62 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
Output,
EventEmitter
} from '@angular/core';
import { UserInfoF } from '@ucap/protocol-query';
@Component({
selector: 'ucap-organization-profile-list-item-02',
templateUrl: './profile-list-item-02.component.html',
styleUrls: ['./profile-list-item-02.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProfileListItem02Component implements OnInit, OnDestroy {
@Input()
userInfo: UserInfoF;
@Input()
authCall = false;
@Input()
checkable = true;
@Input()
checked = false;
@Output()
changeCheck = new EventEmitter<{
checked: boolean;
userInfo: UserInfoF;
}>();
private ngOnDestroySubject: Subject<void>;
constructor(private changeDetectorRef: ChangeDetectorRef) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onChangeCheck(checked: boolean): void {
this.changeCheck.emit({
checked,
userInfo: this.userInfo
});
}
}

View File

@ -1,4 +1,4 @@
<div class="ucap-organization-profile-list-container"> <div class="ucap-organization-profile-list-container" fxFlexFill>
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill> <cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
<ng-container *cdkVirtualFor="let userInfo of userInfos"> <ng-container *cdkVirtualFor="let userInfo of userInfos">
<ng-container <ng-container

View File

@ -1,27 +1,31 @@
<div class="ucap-organization-profile-selection-01-container"> <div class="ucap-organization-profile-selection-01-container" fxLayout="column">
<div class="ucap-organization-profile-selection-01-body"> <div
*ngIf="!!headerTemplate"
class="ucap-organization-profile-selection-01-header"
fxFlex="0 0 48px"
>
<ng-container [ngTemplateOutlet]="headerTemplate"></ng-container>
</div>
<div class="ucap-organization-profile-selection-01-body" fxFlex="1 1 auto">
<mat-chip-list aria-label="User selection" perfectScrollbar> <mat-chip-list aria-label="User selection" perfectScrollbar>
<mat-chip <mat-chip
*ngFor="let profileSelection of _profileSelectionList" *ngFor="let userInfo of _userInfoList"
[color]="profileSelection.color" [color]="_colorFor(userInfo)"
[selectable]="profileSelection.selectable" [removable]="_removableFor(userInfo)"
[selected]="profileSelection.selected" (removed)="_onRemoved($event, userInfo)"
[removable]="profileSelection.removable"
(selectionChange)="
_onSelectionChange($event, profileSelection.userInfo)
"
(removed)="_onRemoved($event, profileSelection.userInfo)"
> >
{{ profileSelection.userInfo.name }} {{ userInfo.name }}
<mat-icon matChipRemove *ngIf="profileSelection.removable"> <mat-icon matChipRemove *ngIf="removableFor(userInfo)">
clear clear
</mat-icon> </mat-icon>
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
<div class="ucap-organization-profile-selection-01-actions"> <div
<ng-content *ngIf="!!actionsTemplate"
select="[ucapOrganizationSelectedProfileList01='action']" class="ucap-organization-profile-selection-01-actions"
></ng-content> fxFlex="0 0 60px"
>
<ng-container [ngTemplateOutlet]="actionsTemplate"></ng-container>
</div> </div>
</div> </div>

View File

@ -3,4 +3,9 @@
.ucap-organization-profile-selection-01-container { .ucap-organization-profile-selection-01-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
.ucap-organization-profile-selection-01-body {
width: 100%;
height: 100%;
}
} }

View File

@ -7,11 +7,15 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Output, Output,
EventEmitter EventEmitter,
Input,
Directive,
ContentChild,
TemplateRef
} from '@angular/core'; } from '@angular/core';
import { UserInfo } from '@ucap/protocol-sync'; import { UserInfo } from '@ucap/protocol-sync';
import { MatChipSelectionChange, MatChipEvent } from '@angular/material/chips'; import { MatChipEvent } from '@angular/material/chips';
export interface ProfileSelection { export interface ProfileSelection {
userInfo: UserInfo; userInfo: UserInfo;
@ -21,6 +25,16 @@ export interface ProfileSelection {
removable?: boolean; removable?: boolean;
} }
@Directive({
selector: '[ucapOrganizationProfileSelection01Header]'
})
export class ProfileSelection01HeaderDirective {}
@Directive({
selector: '[ucapOrganizationProfileSelection01Actions]'
})
export class ProfileSelection01ActionsDirective {}
@Component({ @Component({
selector: 'ucap-organization-profile-selection-01', selector: 'ucap-organization-profile-selection-01',
templateUrl: './profile-selection-01.component.html', templateUrl: './profile-selection-01.component.html',
@ -28,17 +42,34 @@ export interface ProfileSelection {
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ProfileSelection01Component implements OnInit, OnDestroy { export class ProfileSelection01Component implements OnInit, OnDestroy {
@Output() @Input()
selectionChange: EventEmitter<{ set userInfoList(value: UserInfo[]) {
selected: boolean; console.log('userInfoList', value);
userInfo: UserInfo; this._userInfoList = value;
}> = new EventEmitter(); }
// tslint:disable-next-line: variable-name
_userInfoList: UserInfo[];
@Input()
removableFor: (userInfo: UserInfo) => boolean;
@Input()
colorFor: (userInfo: UserInfo) => string;
@Output() @Output()
removed: EventEmitter<UserInfo> = new EventEmitter(); removed: EventEmitter<UserInfo> = new EventEmitter();
// tslint:disable-next-line: variable-name @ContentChild(ProfileSelection01HeaderDirective, {
_profileSelectionList: ProfileSelection[] = []; read: TemplateRef,
static: false
})
headerTemplate: TemplateRef<ProfileSelection01HeaderDirective>;
@ContentChild(ProfileSelection01ActionsDirective, {
read: TemplateRef,
static: false
})
actionsTemplate: TemplateRef<ProfileSelection01ActionsDirective>;
private ngOnDestroySubject: Subject<void>; private ngOnDestroySubject: Subject<void>;
@ -55,54 +86,21 @@ export class ProfileSelection01Component implements OnInit, OnDestroy {
} }
} }
addProfileSelection(profileSelectionList: ProfileSelection[]): void { _colorFor(userInfo: UserInfo): string {
if (!profileSelectionList || 0 === profileSelectionList.length) { if (!this.colorFor) {
return; return undefined;
}
return this.colorFor(userInfo);
} }
profileSelectionList.forEach((v) => { _removableFor(userInfo: UserInfo): boolean {
const i = this.indexOfProfileSelection(v); if (!this.removableFor) {
if (-1 === i) { return undefined;
this._profileSelectionList.push(v);
} }
}); return this.removableFor(userInfo);
}
removeProfileSelection(profileSelectionList: ProfileSelection[]): void {
if (!profileSelectionList || 0 === profileSelectionList.length) {
return;
}
profileSelectionList.forEach((v) => {
const i = this.indexOfProfileSelection(v);
if (-1 < i) {
this._profileSelectionList.splice(i, 1);
}
});
}
_onSelectionChange(event: MatChipSelectionChange, userInfo: UserInfo) {
this.selectionChange.emit({
selected: event.selected,
userInfo
});
} }
_onRemoved(event: MatChipEvent, userInfo: UserInfo) { _onRemoved(event: MatChipEvent, userInfo: UserInfo) {
this.removed.emit(userInfo); this.removed.emit(userInfo);
} }
private indexOfProfileSelection(profileSelection: ProfileSelection): number {
if (
!this._profileSelectionList ||
0 === this._profileSelectionList.length ||
!profileSelection
) {
return -1;
}
return this._profileSelectionList.findIndex(
(v) => v.userInfo.seq === profileSelection.userInfo.seq
);
}
} }

View File

@ -28,6 +28,7 @@
matInput matInput
#inputSearchWord #inputSearchWord
[placeholder]="placeholder" [placeholder]="placeholder"
[value]="!!defaultSearchWord ? defaultSearchWord : ''"
(keyup.enter)="onKeyupEnter($event)" (keyup.enter)="onKeyupEnter($event)"
/> />
<button <button

View File

@ -14,8 +14,8 @@
display: flex; display: flex;
border: { border: {
width: var(--ucap-organization-search-for-tenant-border-width, 1px); width: var(--ucap-organization-search-for-tenant-border-width);
color: var(--ucap-organization-search-for-tenant-border-color, #e42f66); color: var(--ucap-organization-search-for-tenant-border-color);
style: solid; style: solid;
} }

View File

@ -23,7 +23,6 @@ export default {
export const Default = () => ({ export const Default = () => ({
component: SearchForTenantComponent, component: SearchForTenantComponent,
props: { props: {
text: 'Hello SearchForTenantComponent',
companyList: [ companyList: [
{ {
companyCode: 'CD1', companyCode: 'CD1',

View File

@ -29,6 +29,9 @@ export class SearchForTenantComponent implements OnInit, OnDestroy {
@Input() @Input()
defaultCompany: string; defaultCompany: string;
@Input()
defaultSearchWord: string;
@Input() @Input()
placeholder: string; placeholder: string;

View File

@ -4,21 +4,25 @@
*cdkVirtualFor="let node of dataSource.expandedData$" *cdkVirtualFor="let node of dataSource.expandedData$"
></ng-container> ></ng-container>
<mat-tree #treeList [dataSource]="dataSource" [treeControl]="treeControl"> <mat-tree
#treeList
[dataSource]="dataSource"
[treeControl]="treeControl"
[trackBy]="trackBy"
>
<mat-tree-node <mat-tree-node
*matTreeNodeDef="let node" *matTreeNodeDef="let node"
matTreeNodePadding matTreeNodePadding
matTreeNodePaddingIndent="20" matTreeNodePaddingIndent="20"
class="tree-no-child" class="tree-no-child"
(click)="onClickNode($event, node)"
> >
<li <li
(click)="onClickNode(node)"
matRipple
[ngClass]=" [ngClass]="
currentDeptSeq === node?.data?.deptInfo?.seq ? 'current' : '' currentDeptSeq === node?.data?.deptInfo?.seq ? 'current' : ''
" "
> >
<div class="tree-node-body"> <div matRipple class="tree-node-body">
{{ node?.data?.deptInfo | ucapOrganizationTranslate: 'name' }} {{ node?.data?.deptInfo | ucapOrganizationTranslate: 'name' }}
</div> </div>
</li> </li>
@ -29,9 +33,11 @@
matTreeNodePadding matTreeNodePadding
matTreeNodePaddingIndent="20" matTreeNodePaddingIndent="20"
class="tree-has-child" class="tree-has-child"
(click)="onClickNode($event, node)"
> >
<li (click)="onClickNode(node)" matRipple> <li>
<div <div
matRipple
class="tree-node-body" class="tree-node-body"
[ngClass]=" [ngClass]="
currentDeptSeq === node?.data?.deptInfo?.seq ? 'current' : '' currentDeptSeq === node?.data?.deptInfo?.seq ? 'current' : ''

View File

@ -30,6 +30,7 @@ import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
export interface OrganizationNode { export interface OrganizationNode {
deptInfo: DeptInfo; deptInfo: DeptInfo;
ancestorSeq?: number;
children?: OrganizationNode[]; children?: OrganizationNode[];
} }
@ -94,7 +95,9 @@ export class TreeComponent implements OnInit, OnDestroy {
} }
if (nodeMap.has(deptInfo.parentSeq)) { if (nodeMap.has(deptInfo.parentSeq)) {
nodeMap.get(deptInfo.parentSeq).children.push(node); const ancestor = nodeMap.get(deptInfo.parentSeq);
node.ancestorSeq = ancestor.deptInfo.seq;
ancestor.children.push(node);
} else { } else {
remainChildNodeList.push(node); remainChildNodeList.push(node);
} }
@ -110,28 +113,32 @@ export class TreeComponent implements OnInit, OnDestroy {
rootNodeList = [...rootNodeList[0].children]; rootNodeList = [...rootNodeList[0].children];
} }
this._deptInfoList = deptInfoList;
remainChildNodeList.forEach((node) => { remainChildNodeList.forEach((node) => {
if (nodeMap.has(node.deptInfo.parentSeq)) { const ancestor = nodeMap.get(node.deptInfo.parentSeq);
nodeMap.get(node.deptInfo.parentSeq).children.push(node); if (!!ancestor) {
node.ancestorSeq = ancestor.deptInfo.seq;
ancestor.children.push(node);
} }
}); });
this.dataSource.data = rootNodeList; this.dataSource.data = rootNodeList;
if (!!data.expanded) { if (!!data.expanded) {
data.expanded.forEach((s) => { this.expand(...data.expanded);
this.expand(s);
});
} }
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
} }
currentDeptSeq: number; @Output() currentDeptSeqChange: EventEmitter<number> = new EventEmitter();
@Input() set currentDeptSeq(deptInfoSeq: number) {
this._currentDeptSeq = deptInfoSeq;
}
get currentDeptSeq() {
return this._currentDeptSeq;
}
// tslint:disable-next-line: variable-name // tslint:disable-next-line: variable-name
_deptInfoList: DeptInfo[]; _currentDeptSeq: number;
@Output() @Output()
clickNode = new EventEmitter<DeptInfo>(); clickNode = new EventEmitter<DeptInfo>();
@ -201,19 +208,32 @@ export class TreeComponent implements OnInit, OnDestroy {
hasChild = (_: number, node: FlatNode) => node.expandable; hasChild = (_: number, node: FlatNode) => node.expandable;
onClickNode(node: FlatNode) { trackBy = (_: number, node: FlatNode) => node.data.deptInfo.seq;
onClickNode(event: Event, node: FlatNode) {
event.stopPropagation();
this.clickNode.emit(node.data.deptInfo); this.clickNode.emit(node.data.deptInfo);
} }
expand(deptSeq: number) { expand(...deptSeq: number[]) {
if (!this.treeControl.dataNodes) { if (!this.treeControl.dataNodes || !deptSeq || 0 === deptSeq.length) {
return; return;
} }
const flatNodes: FlatNode[] = [];
deptSeq.forEach((s) => {
const node = this.treeControl.dataNodes.find( const node = this.treeControl.dataNodes.find(
(n) => n.data.deptInfo.seq === deptSeq (n) => n.data.deptInfo.seq === s
); );
if (!!node) { if (!!node) {
this.treeControl.expand(node); flatNodes.push(node);
this.selectHierarchy(flatNodes, node);
}
});
if (0 < flatNodes.length) {
this.treeControl.expansionModel.select(...flatNodes);
} }
} }
@ -236,4 +256,30 @@ export class TreeComponent implements OnInit, OnDestroy {
collapseAll() { collapseAll() {
this.treeControl.collapseAll(); this.treeControl.collapseAll();
} }
private selectHierarchy(flatNodes: FlatNode[], flatNode: FlatNode): void {
// tslint:disable-next-line: variable-name
let _flatNode = flatNode;
while (true) {
if (!_flatNode) {
return;
}
const ancestorSeq = _flatNode.data.ancestorSeq;
if (!ancestorSeq) {
return;
}
_flatNode = this.treeControl.dataNodes.find(
(n) => n.data.deptInfo.seq === ancestorSeq
);
if (!_flatNode) {
return;
}
const i = flatNodes.findIndex(
(n) => n.data.deptInfo.seq === _flatNode.data.deptInfo.seq
);
if (-1 === i) {
flatNodes.push(_flatNode);
}
}
}
} }

View File

@ -25,12 +25,17 @@ import { _MODULE_CONFIG } from './config/token';
import { Profile01Component } from './components/profile-01.component'; import { Profile01Component } from './components/profile-01.component';
import { ProfileListItem01Component } from './components/profile-list-item-01.component'; import { ProfileListItem01Component } from './components/profile-list-item-01.component';
import { ProfileListItem02Component } from './components/profile-list-item-02.component';
import { ProfileImage01Component } from './components/profile-image-01.component'; import { ProfileImage01Component } from './components/profile-image-01.component';
import { import {
ProfileListComponent, ProfileListComponent,
ProfileListNodeDirective ProfileListNodeDirective
} from './components/profile-list.component'; } from './components/profile-list.component';
import { ProfileSelection01Component } from './components/profile-selection-01.component'; import {
ProfileSelection01Component,
ProfileSelection01HeaderDirective,
ProfileSelection01ActionsDirective
} from './components/profile-selection-01.component';
import { SearchForTenantComponent } from './components/search-for-tenant.component'; import { SearchForTenantComponent } from './components/search-for-tenant.component';
import { TreeComponent } from './components/tree.component'; import { TreeComponent } from './components/tree.component';
@ -42,6 +47,7 @@ import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
const COMPONENTS = [ const COMPONENTS = [
Profile01Component, Profile01Component,
ProfileListItem01Component, ProfileListItem01Component,
ProfileListItem02Component,
ProfileListComponent, ProfileListComponent,
ProfileImage01Component, ProfileImage01Component,
ProfileSelection01Component, ProfileSelection01Component,
@ -50,7 +56,11 @@ const COMPONENTS = [
]; ];
const DIALOGS = []; const DIALOGS = [];
const PIPES = [TranslatePipe]; const PIPES = [TranslatePipe];
const DIRECTIVES = [ProfileListNodeDirective]; const DIRECTIVES = [
ProfileListNodeDirective,
ProfileSelection01HeaderDirective,
ProfileSelection01ActionsDirective
];
const SERVICES = [TranslateService]; const SERVICES = [TranslateService];
@NgModule({ @NgModule({

View File

@ -163,7 +163,7 @@ export class PresenceUtil {
return statusBulkInfo.phoneStatus; return statusBulkInfo.phoneStatus;
case PresenceType.CONFERENCE: case PresenceType.CONFERENCE:
return statusBulkInfo.conferenceStatus; return statusBulkInfo.conferenceStatus;
case PresenceType.IMESSENER: case PresenceType.IMESSENGER:
return statusBulkInfo.imessengerStatus; return statusBulkInfo.imessengerStatus;
default: default:
return undefined; return undefined;