Merge branch 'master' into new-org

This commit is contained in:
leejinho 2020-03-25 16:26:10 +09:00
commit 62af089e67
77 changed files with 1775 additions and 420 deletions

Binary file not shown.

View File

@ -59,6 +59,10 @@
{ {
"from": "./config/build/win/bin/AeroAdmin.exe", "from": "./config/build/win/bin/AeroAdmin.exe",
"to": "./bin/AeroAdmin.exe" "to": "./bin/AeroAdmin.exe"
},
{
"from": "./config/build/win/bin/config.dat",
"to": "./bin/config.dat"
} }
] ]
}, },

View File

@ -27,7 +27,8 @@ export enum ElectronBrowserWindowChannel {
Close = 'close', Close = 'close',
Closed = 'closed', Closed = 'closed',
ReadyToShow = 'ready-to-show', ReadyToShow = 'ready-to-show',
Focus = 'focus' Focus = 'focus',
Blur = 'blur'
} }
export enum ElectronWebContentsChannel { export enum ElectronWebContentsChannel {

View File

@ -14,8 +14,9 @@ import {
ElectronWebContentsChannel ElectronWebContentsChannel
} from '@ucap-webmessenger/electron-core'; } from '@ucap-webmessenger/electron-core';
import { appStorage } from '../lib/storage';
import { now } from '../util/now'; import { now } from '../util/now';
import { Storage } from '../lib/storage';
import { WindowStateChannel } from '@ucap-webmessenger/native-electron';
export class AppWindow { export class AppWindow {
private window: BrowserWindow | null = null; private window: BrowserWindow | null = null;
@ -33,7 +34,7 @@ export class AppWindow {
private defaultWidth = 1024; private defaultWidth = 1024;
private defaultHeight = 768; private defaultHeight = 768;
public constructor(private appIconPath: string) { public constructor(private appIconPath: string, appStorage: Storage) {
const savedWindowState = windowStateKeeper({ const savedWindowState = windowStateKeeper({
defaultWidth: this.defaultWidth, defaultWidth: this.defaultWidth,
defaultHeight: this.defaultHeight defaultHeight: this.defaultHeight
@ -60,6 +61,7 @@ export class AppWindow {
}, },
acceptFirstMouse: true, acceptFirstMouse: true,
icon: this.appIconPath, icon: this.appIconPath,
fullscreenable: false,
show: false show: false
}; };
@ -84,6 +86,20 @@ export class AppWindow {
event.returnValue = true; event.returnValue = true;
}); });
// windows Focus or Blur state detacted.
this.window.on(ElectronBrowserWindowChannel.Focus, () => {
this.window.webContents.send(
WindowStateChannel.FocuseChanged,
ElectronBrowserWindowChannel.Focus
);
});
this.window.on(ElectronBrowserWindowChannel.Blur, () => {
this.window.webContents.send(
WindowStateChannel.FocuseChanged,
ElectronBrowserWindowChannel.Blur
);
});
// on macOS, when the user closes the window we really just hide it. This // on macOS, when the user closes the window we really just hide it. This
// lets us activate quickly and keep all our interesting logic in the // lets us activate quickly and keep all our interesting logic in the
// renderer. // renderer.
@ -122,11 +138,14 @@ export class AppWindow {
// can be tidied up once https://github.com/electron/electron/issues/12971 // can be tidied up once https://github.com/electron/electron/issues/12971
// has been confirmed as resolved // has been confirmed as resolved
this.window.once(ElectronBrowserWindowChannel.ReadyToShow, () => { this.window.once(ElectronBrowserWindowChannel.ReadyToShow, () => {
if (appStorage.startupHideWindow) { this.window.close();
setTimeout(() => {
if (!!appStorage && appStorage.startupHideWindow) {
this.window.close(); this.window.close();
} else { } else {
this.window.show(); this.window.show();
} }
}, 500);
this.window.on(ElectronBrowserWindowChannel.Unmaximize, () => { this.window.on(ElectronBrowserWindowChannel.Unmaximize, () => {
setTimeout(() => { setTimeout(() => {
const bounds = this.window.getBounds(); const bounds = this.window.getBounds();

View File

@ -61,7 +61,7 @@ import {
import log from 'electron-log'; import log from 'electron-log';
import { RendererUpdater } from './lib/renderer-updater'; import { RendererUpdater } from './lib/renderer-updater';
import { appStorage } from './lib/storage'; import { Storage } from './lib/storage';
require('v8-compile-cache'); require('v8-compile-cache');
@ -108,6 +108,8 @@ let updateWindowService: ElectronUpdateWindowService | null;
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
let appStorage: Storage;
function handleUncaughtException(error: Error) { function handleUncaughtException(error: Error) {
preventQuit = true; preventQuit = true;
@ -174,7 +176,12 @@ if (isDuplicateInstance) {
} }
function createWindow() { function createWindow() {
const window = new AppWindow(appIconPath); try {
appStorage = new Storage();
} catch (e) {
log.error('[appStorage = new Storage()]]', e);
}
const window = new AppWindow(appIconPath, appStorage);
if (__DEV__) { if (__DEV__) {
// const { // const {
@ -212,7 +219,8 @@ function createWindow() {
}); });
window.onDidLoad(() => { window.onDidLoad(() => {
if (!appStorage.startupHideWindow) { if (!!appStorage && !appStorage.startupHideWindow) {
// not used?
window.show(); window.show();
} else { } else {
window.hide(); window.hide();
@ -440,8 +448,10 @@ ipcMain.on(
ipcMain.on( ipcMain.on(
MessengerChannel.ClearAppStorage, MessengerChannel.ClearAppStorage,
(event: IpcMainEvent, ...args: any[]) => { (event: IpcMainEvent, ...args: any[]) => {
if (!!!!appStorage) {
appStorage.reset(); appStorage.reset();
} }
}
); );
ipcMain.on( ipcMain.on(
@ -478,6 +488,7 @@ ipcMain.on(
(event: IpcMainEvent, ...args: any[]) => { (event: IpcMainEvent, ...args: any[]) => {
const isStartupHideWindow = args[0] as boolean; const isStartupHideWindow = args[0] as boolean;
if (!!!!appStorage) {
appStorage.startupHideWindow = isStartupHideWindow; appStorage.startupHideWindow = isStartupHideWindow;
log.info( log.info(
'StartupHideWindow is changed from ', 'StartupHideWindow is changed from ',
@ -485,6 +496,7 @@ ipcMain.on(
' to ', ' to ',
appStorage.startupHideWindow appStorage.startupHideWindow
); );
}
event.returnValue = true; event.returnValue = true;
} }
); );
@ -494,7 +506,7 @@ ipcMain.on(
(event: IpcMainEvent, ...args: any[]) => { (event: IpcMainEvent, ...args: any[]) => {
const downloadPath = args[0] as string; const downloadPath = args[0] as string;
if (!!downloadPath && downloadPath.length > 0) { if (!!appStorage && !!downloadPath && downloadPath.length > 0) {
appStorage.downloadPath = downloadPath; appStorage.downloadPath = downloadPath;
log.info('downloadPath is changed to ', appStorage.downloadPath); log.info('downloadPath is changed to ', appStorage.downloadPath);
@ -565,9 +577,9 @@ ipcMain.on(
let basePath = path.join( let basePath = path.join(
app.getPath('documents'), app.getPath('documents'),
appStorage.constDefaultDownloadFolder !!appStorage ? appStorage.constDefaultDownloadFolder : ''
); );
if (!!appStorage.downloadPath) { if (!!appStorage && !!appStorage.downloadPath) {
basePath = appStorage.downloadPath; basePath = appStorage.downloadPath;
} }
try { try {
@ -607,7 +619,7 @@ ipcMain.on(
const make: boolean = args[1]; const make: boolean = args[1];
if (!folderItem) { if (!folderItem) {
let basePath = app.getPath('downloads'); let basePath = app.getPath('downloads');
if (!!appStorage.downloadPath) { if (!!appStorage && !!appStorage.downloadPath) {
try { try {
basePath = appStorage.downloadPath; basePath = appStorage.downloadPath;
} catch (err) { } catch (err) {
@ -788,6 +800,10 @@ ipcMain.on(
} }
}); });
if (!appWindow.isVisible()) {
appWindow.browserWindow.minimize();
}
appWindow.browserWindow.flashFrame(true); appWindow.browserWindow.flashFrame(true);
} }
); );
@ -821,6 +837,9 @@ ipcMain.on(ClipboardChannel.Read, (event: IpcMainEvent, ...args: any[]) => {
ipcMain.on(AppChannel.Exit, (event: IpcMainEvent, ...args: any[]) => { ipcMain.on(AppChannel.Exit, (event: IpcMainEvent, ...args: any[]) => {
appExit(); appExit();
}); });
ipcMain.on(AppChannel.Logging, (event: IpcMainEvent, ...args: any[]) => {
log.error('[G]', args[0]);
});
ipcMain.on(ExternalChannel.OpenUrl, (event: IpcMainEvent, ...args: any[]) => { ipcMain.on(ExternalChannel.OpenUrl, (event: IpcMainEvent, ...args: any[]) => {
const targetUrl = args[0]; const targetUrl = args[0];

View File

@ -11,6 +11,7 @@ export class Storage {
constructor() { constructor() {
this.store = new ElectronStore({ this.store = new ElectronStore({
cwd: path.join(__dirname, '..', '..', '..', '/bin/'),
schema: { schema: {
options: { options: {
type: 'object', type: 'object',

View File

@ -1,6 +1,6 @@
{ {
"name": "ucap-webmessenger", "name": "ucap-webmessenger",
"version": "1.0.4", "version": "1.0.13",
"author": { "author": {
"name": "LG CNS", "name": "LG CNS",
"email": "lgucap@lgcns.com" "email": "lgucap@lgcns.com"
@ -45,7 +45,7 @@
"@angular/core": "^8.2.14", "@angular/core": "^8.2.14",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"electron-log": "^3.0.9", "electron-log": "^3.0.9",
"electron-store": "^5.1.0", "electron-store": "^5.1.1",
"electron-updater": "^4.2.0", "electron-updater": "^4.2.0",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"file-type": "^14.1.2", "file-type": "^14.1.2",

View File

@ -192,6 +192,14 @@ export const decodeUrlInfoDaesang: APIDecoder<DaesangUrlInfoResponse> = (
url: arr.length > 1 ? arr[1] : arr[0] url: arr.length > 1 ? arr[1] : arr[0]
}); });
} }
if (!!res.WebLinkERP) {
const arr = res.WebLinkERP.split(',');
webLink.push({
key: WebLinkType.Erp,
title: arr.length > 1 ? arr[0] : '',
url: arr.length > 1 ? arr[1] : arr[0]
});
}
return { return {
statusCode: res.StatusCode, statusCode: res.StatusCode,

View File

@ -0,0 +1,20 @@
import { HttpParameterCodec } from '@angular/common/http';
export class HttpUrlEncodingCodec implements HttpParameterCodec {
encodeKey(k: string): string {
return this.standardEncoding(k);
}
encodeValue(v: string): string {
return this.standardEncoding(v);
}
decodeKey(k: string): string {
return decodeURIComponent(k);
}
decodeValue(v: string) {
return decodeURIComponent(v);
}
standardEncoding(v: string): string {
return encodeURIComponent(v);
}
}

View File

@ -3,6 +3,7 @@
*/ */
export * from './lib/apis/api'; export * from './lib/apis/api';
export * from './lib/apis/httpUrlEncodingCodec';
export * from './lib/types/message-status-code.type'; export * from './lib/types/message-status-code.type';
export * from './lib/types/status-code.type'; export * from './lib/types/status-code.type';

View File

@ -50,6 +50,7 @@ import { AppMessengerLayoutModule } from './layouts/messenger/messenger.layout.m
import { AppNativeLayoutModule } from './layouts/native/native.layout.module'; import { AppNativeLayoutModule } from './layouts/native/native.layout.module';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { ERRORHANDLER } from './error-handler';
@NgModule({ @NgModule({
imports: [ imports: [
@ -105,7 +106,7 @@ import { environment } from '../environments/environment';
level: NgxLoggerLevel.DEBUG level: NgxLoggerLevel.DEBUG
}) })
], ],
providers: [...GUARDS], providers: [...GUARDS, ERRORHANDLER],
declarations: [AppComponent], declarations: [AppComponent],
bootstrap: [AppComponent], bootstrap: [AppComponent],
entryComponents: [] entryComponents: []

View File

@ -0,0 +1,29 @@
import { ErrorHandler, Injectable, Injector, NgZone } from '@angular/core';
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
import { error } from 'console';
@Injectable({
providedIn: 'root'
})
export class AppGlobalErrorhandler implements ErrorHandler {
constructor(private injector: Injector, private zone: NgZone) {}
// tslint:disable-next-line: no-shadowed-variable
handleError(error: Error) {
let nativeLogging: any;
try {
const nativeService: NativeService = this.injector.get(
UCAP_NATIVE_SERVICE
);
nativeService.appLogging(error.stack);
nativeLogging = 'SUCCESS';
} catch (e) {
nativeLogging = e;
}
console.groupCollapsed('App Global Error Logging');
console.log('App log', error);
console.log('Native Logging', nativeLogging);
console.groupEnd();
}
}

View File

@ -0,0 +1,6 @@
import { AppGlobalErrorhandler } from './global.errorhandler';
import { ErrorHandler } from '@angular/core';
export const ERRORHANDLER = [
{ provide: ErrorHandler, useClass: AppGlobalErrorhandler }
];

View File

@ -49,7 +49,7 @@ export class AppAutoLoginGuard implements CanActivate {
if ( if (
!!appUserInfo && !!appUserInfo &&
appUserInfo.settings.general.autoLogin && !!appUserInfo.settings.general.autoLogin &&
!(!!personLogout && !!personLogout.personLogout) !(!!personLogout && !!personLogout.personLogout)
) { ) {
this.store.dispatch( this.store.dispatch(

View File

@ -1,6 +1,9 @@
<ucap-file-viewer <ucap-file-viewer
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
[imageOnly]="imageOnly"
[imageOnlyData]="imageOnlyData"
(download)="onDownload($event)" (download)="onDownload($event)"
(saveAs)="onSaveAs($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-file-viewer> ></ucap-file-viewer>

View File

@ -7,6 +7,9 @@ import { DeviceType } from '@ucap-webmessenger/core';
import { FileDownloadItem } from '@ucap-webmessenger/api'; import { FileDownloadItem } from '@ucap-webmessenger/api';
import { CommonApiService } from '@ucap-webmessenger/api-common'; import { CommonApiService } from '@ucap-webmessenger/api-common';
import { AppFileService } from '@app/services/file.service'; import { AppFileService } from '@app/services/file.service';
import { ImageOnlyDataInfo, SnackBarService } from '@ucap-webmessenger/ui';
import { UCAP_NATIVE_SERVICE, NativeService } from '@ucap-webmessenger/native';
import { TranslateService } from '@ngx-translate/core';
export interface FileViewerDialogData { export interface FileViewerDialogData {
fileInfo: FileEventJson; fileInfo: FileEventJson;
@ -14,6 +17,8 @@ export interface FileViewerDialogData {
userSeq: number; userSeq: number;
deviceType: DeviceType; deviceType: DeviceType;
token: string; token: string;
imageOnly?: boolean;
imageOnlyData?: ImageOnlyDataInfo;
} }
export interface FileViewerDialogResult {} export interface FileViewerDialogResult {}
@ -32,6 +37,9 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
fileDownloadUrl: string; fileDownloadUrl: string;
imageOnly = false;
imageOnlyData: ImageOnlyDataInfo;
constructor( constructor(
public dialogRef: MatDialogRef< public dialogRef: MatDialogRef<
FileViewerDialogData, FileViewerDialogData,
@ -40,6 +48,9 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
@Inject(MAT_DIALOG_DATA) public data: FileViewerDialogData, @Inject(MAT_DIALOG_DATA) public data: FileViewerDialogData,
private commonApiService: CommonApiService, private commonApiService: CommonApiService,
private appFileService: AppFileService, private appFileService: AppFileService,
private translateService: TranslateService,
private snackBarService: SnackBarService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private logger: NGXLogger private logger: NGXLogger
) { ) {
this.fileInfo = data.fileInfo; this.fileInfo = data.fileInfo;
@ -47,7 +58,17 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
this.userSeq = data.userSeq; this.userSeq = data.userSeq;
this.deviceType = data.deviceType; this.deviceType = data.deviceType;
this.token = data.token; this.token = data.token;
}
ngOnInit() {
if (!!this.data.imageOnly) {
this.imageOnly = this.data.imageOnly;
this.imageOnlyData = this.data.imageOnlyData;
}
if (!!this.imageOnly) {
this.fileDownloadUrl = this.imageOnlyData.imageUrl;
} else {
this.fileDownloadUrl = this.commonApiService.urlForFileTalkDownload( this.fileDownloadUrl = this.commonApiService.urlForFileTalkDownload(
{ {
userSeq: this.userSeq, userSeq: this.userSeq,
@ -58,8 +79,7 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
this.downloadUrl this.downloadUrl
); );
} }
}
ngOnInit() {}
ngOnDestroy(): void {} ngOnDestroy(): void {}
@ -76,6 +96,47 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
fileDownloadUrl: this.downloadUrl fileDownloadUrl: this.downloadUrl
}); });
} }
onSaveAs(fileDownloadItem: FileDownloadItem): void {
this.nativeService
.selectSaveFilePath(this.fileInfo.fileName)
.then(result => {
if (!result) {
return;
}
if (result.canceled) {
// this.snackBarService.open(
// this.translateService.instant('common.file.results.canceled'),
// this.translateService.instant('common.file.errors.label'),
// {
// duration: 1000
// }
// );
} else {
this.saveFile(fileDownloadItem, result.filePath);
}
})
.catch(reason => {
this.snackBarService.open(
this.translateService.instant('common.file.errors.failToSpecifyPath'),
this.translateService.instant('common.file.errors.label')
);
});
}
saveFile(fileDownloadItem: FileDownloadItem, savePath?: string) {
this.appFileService.fileTalkDownlod({
req: {
userSeq: this.userSeq,
deviceType: this.deviceType,
token: this.token,
attachmentsSeq: this.fileInfo.attachmentSeq,
fileDownloadItem
},
fileName: this.fileInfo.fileName,
savePath
});
}
onClosedViewer(): void { onClosedViewer(): void {
this.dialogRef.close(); this.dialogRef.close();

View File

@ -20,7 +20,8 @@ import { Subscription, combineLatest, Observable } from 'rxjs';
import { import {
RoomInfo, RoomInfo,
UserInfoShort, UserInfoShort,
UserInfo as RoomUserInfo UserInfo as RoomUserInfo,
RoomType
} from '@ucap-webmessenger/protocol-room'; } from '@ucap-webmessenger/protocol-room';
import * as AppStore from '@app/store'; import * as AppStore from '@app/store';
import * as ChatStore from '@app/store/messenger/chat'; import * as ChatStore from '@app/store/messenger/chat';
@ -288,11 +289,16 @@ export class ChatComponent implements OnInit, OnDestroy, AfterViewChecked {
value => roomInfo.roomSeq === value.roomSeq value => roomInfo.roomSeq === value.roomSeq
); );
if (-1 < i) { if (-1 < i) {
if (roomInfo.roomType === RoomType.Single) {
// Ignore type of joinRoom for Single Room.
return this.roomUserShortList[i].userInfos;
} else {
return this.roomUserShortList[i].userInfos.filter( return this.roomUserShortList[i].userInfos.filter(
user => user.isJoinRoom user => user.isJoinRoom
); );
} }
} }
}
return []; return [];
} }

View File

@ -318,6 +318,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist)) .pipe(select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist))
.subscribe(userInfoList => { .subscribe(userInfoList => {
this.userInfoListSubject.next(userInfoList); this.userInfoListSubject.next(userInfoList);
this.changeDetectorRef.detectChanges();
}); });
this.eventListProcessing$ = this.store.pipe( this.eventListProcessing$ = this.store.pipe(
@ -1585,7 +1586,32 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
break; break;
case 'ADD_MEMBER': case 'ADD_MEMBER':
{ {
const curRoomUser = this.userInfoListSubject.value.filter( const userInfoList = this.userInfoListSubject.value;
if (
!!userInfoList &&
userInfoList.length >=
environment.productConfig.CommonSetting.maxChatRoomUser
) {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.translateService.instant('chat.errors.label'),
html: this.translateService.instant(
'chat.errors.maxCountOfRoomMemberWith',
{
maxCount:
environment.productConfig.CommonSetting.maxChatRoomUser
}
)
}
});
return;
}
const curRoomUser = userInfoList.filter(
user => user =>
user.seq !== this.loginResSubject.value.userSeq && user.isJoinRoom user.seq !== this.loginResSubject.value.userSeq && user.isJoinRoom
); );

View File

@ -1,8 +1,8 @@
<div fxLayout="column" class="rightDrawer-userlist"> <div fxLayout="column" class="rightDrawer-userlist">
<div class="search-list"> <div class="search-list">
<perfect-scrollbar class="room-user-scrollbar"> <cdk-virtual-scroll-viewport itemSize="60" perfectScrollbar fxFlexFill>
<ucap-profile-user-list-item <ucap-profile-user-list-item
*ngFor="let userInfo of userInfoList" *cdkVirtualFor="let userInfo of userInfoList"
[userInfo]="userInfo" [userInfo]="userInfo"
[presence]="getStatusBulkInfo(userInfo) | async" [presence]="getStatusBulkInfo(userInfo) | async"
[sessionVerinfo]="sessionVerinfo" [sessionVerinfo]="sessionVerinfo"
@ -10,11 +10,9 @@
(openProfile)="onClickOpenProfile(userInfo.seq)" (openProfile)="onClickOpenProfile(userInfo.seq)"
> >
</ucap-profile-user-list-item> </ucap-profile-user-list-item>
</perfect-scrollbar> </cdk-virtual-scroll-viewport>
</div> </div>
<div <div class="btn-box">
class="btn-box"
>
<button mat-flat-button class="mat-primary" (click)="onClickAddMember()"> <button mat-flat-button class="mat-primary" (click)="onClickAddMember()">
{{ 'chat.addMemberToRoom' | translate }} {{ 'chat.addMemberToRoom' | translate }}
</button> </button>

View File

@ -6,7 +6,7 @@ import {
EventEmitter, EventEmitter,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription, combineLatest } from 'rxjs';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { tap, map, take } from 'rxjs/operators'; import { tap, map, take } from 'rxjs/operators';
@ -21,7 +21,10 @@ import {
DialogService, DialogService,
ConfirmDialogComponent, ConfirmDialogComponent,
ConfirmDialogResult, ConfirmDialogResult,
ConfirmDialogData ConfirmDialogData,
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap-webmessenger/ui'; } from '@ucap-webmessenger/ui';
import { import {
SelectGroupDialogComponent, SelectGroupDialogComponent,
@ -43,6 +46,7 @@ import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatMenuTrigger } from '@angular/material/menu'; import { MatMenuTrigger } from '@angular/material/menu';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { environment } from 'projects/ucap-webmessenger-app/src/environments/environment.dev';
@Component({ @Component({
selector: 'app-layout-chat-right-drawer-room-user-list', selector: 'app-layout-chat-right-drawer-room-user-list',
@ -82,16 +86,22 @@ export class RoomUserListComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
this.userInfoListSubscription = this.store this.userInfoListSubscription = combineLatest([
.pipe( this.store.pipe(
select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist), select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist)
tap(userInfoList => { ),
this.store.pipe(select(AppStore.MessengerSelector.RoomSelector.roomInfo))
]).subscribe(([userInfoList, roomInfo]) => {
this.userInfoList = userInfoList this.userInfoList = userInfoList
.filter(userInfo => userInfo.isJoinRoom === true) .filter(userInfo => {
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); if (roomInfo.roomType === RoomType.Single) {
return true;
} else {
return userInfo.isJoinRoom === true;
}
}) })
) .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
.subscribe(); });
this.roomInfoSubscription = this.store this.roomInfoSubscription = this.store
.pipe( .pipe(
@ -139,6 +149,29 @@ export class RoomUserListComponent implements OnInit, OnDestroy {
} }
async onClickAddMember() { async onClickAddMember() {
if (
!!this.userInfoList &&
this.userInfoList.length >=
environment.productConfig.CommonSetting.maxChatRoomUser
) {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.translateService.instant('chat.errors.label'),
html: this.translateService.instant(
'chat.errors.maxCountOfRoomMemberWith',
{
maxCount: environment.productConfig.CommonSetting.maxChatRoomUser
}
)
}
});
return;
}
const curRoomUser = this.userInfoList.filter( const curRoomUser = this.userInfoList.filter(
user => user.seq !== this.loginRes.userSeq && user.isJoinRoom user => user.seq !== this.loginRes.userSeq && user.isJoinRoom
); );

View File

@ -255,15 +255,55 @@
[selected]="getChipsRemoveYn(userInfo)" [selected]="getChipsRemoveYn(userInfo)"
(removed)="onClickDeleteUser(userInfo)" (removed)="onClickDeleteUser(userInfo)"
> >
{{ userInfo.name }} {{ userInfo | ucapTranslate: 'name' }}
<mat-icon matChipRemove *ngIf="getChipsRemoveYn(userInfo)" <mat-icon matChipRemove *ngIf="getChipsRemoveYn(userInfo)"
>clear</mat-icon >clear</mat-icon
> >
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
<ng-container
*ngIf="
data.type === UserSelectDialogType.NewChat;
then newchatcount;
else defaultcount
"
></ng-container>
<ng-template #newchatcount>
<span
[ngClass]="
selectedUserList.length >=
environment.productConfig.CommonSetting.maxChatRoomUser
? 'text-warn-color'
: ''
"
>
{{ selectedUserList.length }} /
{{ environment.productConfig.CommonSetting.maxChatRoomUser - 1 }}
{{ 'common.units.persons' | translate }}
</span>
<span
class="text-warn-color"
style="float: right;"
*ngIf="
selectedUserList.length >=
environment.productConfig.CommonSetting.maxChatRoomUser
"
>
({{
'chat.errors.maxCountOfRoomMemberWith'
| translate
: {
maxCount:
environment.productConfig.CommonSetting.maxChatRoomUser - 1
}
}})
</span>
</ng-template>
<ng-template #defaultcount>
<span> <span>
{{ selectedUserList.length }} {{ selectedUserList.length }}
{{ 'common.units.persons' | translate }} {{ 'common.units.persons' | translate }}
</span> </span>
</ng-template> </ng-template>
</ng-template>

View File

@ -134,6 +134,7 @@ export class CreateChatDialogComponent implements OnInit, OnDestroy {
currentTabIndex: number; currentTabIndex: number;
UserSelectDialogType = UserSelectDialogType; UserSelectDialogType = UserSelectDialogType;
environment = environment;
loginRes: LoginResponse; loginRes: LoginResponse;
loginResSubscription: Subscription; loginResSubscription: Subscription;
@ -740,8 +741,12 @@ export class CreateChatDialogComponent implements OnInit, OnDestroy {
if (this.selectedUserList.length === 0 && !this.selectedRoom) { if (this.selectedUserList.length === 0 && !this.selectedRoom) {
return true; return true;
} }
return false; return false;
} else if (this.data.type === UserSelectDialogType.NewChat) {
return (
this.selectedUserList.length >=
this.environment.productConfig.CommonSetting.maxChatRoomUser
);
} else { } else {
return false; return false;
} }

View File

@ -9,6 +9,9 @@
[openProfileOptions]="data.openProfileOptions" [openProfileOptions]="data.openProfileOptions"
[useBuddyToggleButton]="useBuddyToggleButton" [useBuddyToggleButton]="useBuddyToggleButton"
[authInfo]="authInfo" [authInfo]="authInfo"
[enableElephantButton]="getEnableElephantButton()"
(profileImageView)="onClickProfileImageView()"
(sendElephant)="onClickSendElephant()"
(openChat)="onClickChat($event)" (openChat)="onClickChat($event)"
(sendMessage)="onClickSendMessage($event)" (sendMessage)="onClickSendMessage($event)"
(sendCall)="onClickSendClickToCall($event)" (sendCall)="onClickSendClickToCall($event)"

View File

@ -4,9 +4,13 @@ import {
KEY_LOGIN_RES_INFO, KEY_LOGIN_RES_INFO,
KEY_VER_INFO, KEY_VER_INFO,
KEY_AUTH_INFO, KEY_AUTH_INFO,
MainMenu MainMenu,
KEY_URL_INFO
} from '@app/types'; } from '@app/types';
import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import {
SessionStorageService,
LocalStorageService
} from '@ucap-webmessenger/web-storage';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import * as AppStore from '@app/store'; import * as AppStore from '@app/store';
@ -30,7 +34,8 @@ import {
SnackBarService, SnackBarService,
AlertDialogComponent, AlertDialogComponent,
AlertDialogResult, AlertDialogResult,
AlertDialogData AlertDialogData,
ImageOnlyDataInfo
} from '@ucap-webmessenger/ui'; } from '@ucap-webmessenger/ui';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
@ -57,11 +62,20 @@ import {
ConferenceService ConferenceService
} from '@ucap-webmessenger/api-prompt'; } from '@ucap-webmessenger/api-prompt';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { SmsUtils } from '@ucap-webmessenger/daesang'; import { SmsUtils, WebLinkType } from '@ucap-webmessenger/daesang';
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native'; import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
import { environment } from '../../../../../environments/environment'; import { environment } from '../../../../../environments/environment';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { UserInfoUpdateType } from '@ucap-webmessenger/protocol-info'; import { UserInfoUpdateType } from '@ucap-webmessenger/protocol-info';
import {
FileViewerDialogComponent,
FileViewerDialogData,
FileViewerDialogResult
} from '@app/layouts/common/dialogs/file-viewer.dialog.component';
import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { FileType } from '@ucap-webmessenger/protocol-file';
import { DaesangUrlInfoResponse } from '@ucap-webmessenger/api-external';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
export interface ProfileDialogData { export interface ProfileDialogData {
userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN; userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN;
@ -99,6 +113,7 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
@Inject(MAT_DIALOG_DATA) public data: ProfileDialogData, @Inject(MAT_DIALOG_DATA) public data: ProfileDialogData,
private dialogService: DialogService, private dialogService: DialogService,
private localStorageService: LocalStorageService,
private sessionStorageService: SessionStorageService, private sessionStorageService: SessionStorageService,
private commonApiService: CommonApiService, private commonApiService: CommonApiService,
private conferenceService: ConferenceService, private conferenceService: ConferenceService,
@ -464,6 +479,93 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
); );
} }
onClickProfileImageView() {
if (!this.loginRes || !this.loginRes.userInfo.profileImageFile) {
return;
}
const imageOnlyData: ImageOnlyDataInfo = {
imageUrl: this.userInfo.profileImageFile,
imageRootUrl: this.sessionVerinfo.profileRoot,
defaultImage: 'assets/images/no_image.png',
fileName: this.userInfo.name
};
this.dialogService.open<
FileViewerDialogComponent,
FileViewerDialogData,
FileViewerDialogResult
>(FileViewerDialogComponent, {
position: {
top: '50px'
},
maxWidth: '100vw',
maxHeight: '100vh',
height: 'calc(100% - 50px)',
width: '100%',
panelClass: 'app-dialog-full',
data: {
imageOnly: true,
imageOnlyData,
fileInfo: {},
downloadUrl: this.sessionVerinfo.downloadUrl,
deviceType: this.environmentsInfo.deviceType,
token: this.loginRes.tokenString,
userSeq: this.loginRes.userSeq
}
});
}
getEnableElephantButton() {
if (!this.loginRes) {
return false;
}
const myEmployeeNum = this.loginRes.userInfo.employeeNum;
const profEmployeeNum = this.userInfo.employeeNum;
const trgtEmployeeCode = '10'; // only 10
if (
!this.isMe &&
!!myEmployeeNum &&
!!profEmployeeNum &&
myEmployeeNum.slice(0, 2) === trgtEmployeeCode &&
profEmployeeNum.slice(0, 2) === trgtEmployeeCode
) {
return true;
}
return false;
}
onClickSendElephant() {
const urlInfo: DaesangUrlInfoResponse = this.sessionStorageService.get<
DaesangUrlInfoResponse
>(KEY_URL_INFO);
if (!!urlInfo && !!urlInfo.webLink) {
const links = urlInfo.webLink.filter(
link => link.key === WebLinkType.Elephant
);
if (!!links && links.length > 0) {
const appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
let elephantUrl = links[0].url
.replace(
/(\(%USER_PASS%\))/g,
encodeURIComponent(appUserInfo.loginPw)
) // exchange USER_PASS params
.replace('kind%3D3', 'kind%3D2'); // change value of 'kind'
elephantUrl += '%26empno%3D' + this.userInfo.employeeNum + ','; // add parameter of 'empno'
console.log(elephantUrl);
this.nativeService.openDefaultBrowser(elephantUrl);
}
}
}
onUpdateIntro(intro: string) { onUpdateIntro(intro: string) {
this.store.dispatch( this.store.dispatch(
AuthenticationStore.infoUser({ AuthenticationStore.infoUser({

View File

@ -124,13 +124,6 @@
</div> </div>
</mat-card-content> </mat-card-content>
<mat-card-actions class="button-form flex-row"> <mat-card-actions class="button-form flex-row">
<!-- <button
mat-stroked-button
(click)="onClickClearSettingAndLogout()"
class="mat-primary"
>
Clear Setting & Logout
</button> -->
<button <button
mat-stroked-button mat-stroked-button
(click)="onClickChoice(false)" (click)="onClickChoice(false)"

View File

@ -1,20 +1,12 @@
import { Component, OnInit, Inject, Renderer2, OnDestroy } from '@angular/core'; import { Component, OnInit, Inject, Renderer2, OnDestroy } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { import { KEY_LOGIN_RES_INFO, KEY_VER_INFO } from '@app/types';
KEY_LOGIN_RES_INFO,
KEY_VER_INFO,
KEY_LOGIN_INFO,
KEY_URL_INFO,
KEY_AUTH_INFO,
KEY_LOGOUT_INFO
} from '@app/types';
import { import {
SessionStorageService, SessionStorageService,
LocalStorageService LocalStorageService
} from '@ucap-webmessenger/web-storage'; } from '@ucap-webmessenger/web-storage';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import * as AuthenticationStore from '@app/store/account/authentication';
import clone from 'clone'; import clone from 'clone';
@ -210,17 +202,4 @@ export class MessengerSettingsDialogComponent implements OnInit, OnDestroy {
return modSettings; return modSettings;
} }
onClickClearSettingAndLogout() {
this.localStorageService.remove(KEY_APP_USER_INFO);
this.sessionStorageService.remove(KEY_LOGIN_RES_INFO);
this.sessionStorageService.remove(KEY_VER_INFO);
this.sessionStorageService.remove(KEY_LOGIN_INFO);
this.sessionStorageService.remove(KEY_URL_INFO);
this.sessionStorageService.remove(KEY_AUTH_INFO);
this.sessionStorageService.remove(KEY_LOGOUT_INFO);
this.nativeService.clearAppStorage();
this.dialogService.closeAll();
this.store.dispatch(AuthenticationStore.loginRedirect());
}
} }

View File

@ -103,6 +103,15 @@
> >
<span class="weblink Personal-news">NEWS</span> <span class="weblink Personal-news">NEWS</span>
</button> </button>
<button
mat-icon-button
*ngSwitchCase="WebLinkType.Erp"
class="button"
[matTooltip]="link.title"
(click)="onClickWebLink(link)"
>
<span class="weblink erp">ERP</span>
</button>
<button <button
mat-icon-button mat-icon-button
*ngSwitchCase="WebLinkType.Mail" *ngSwitchCase="WebLinkType.Mail"
@ -447,10 +456,13 @@
</mat-menu> </mat-menu>
<mat-menu #informationMenu="matMenu"> <mat-menu #informationMenu="matMenu">
<ng-template matMenuContent> <ng-template matMenuContent let-isShowClearBtn="false">
<div class="version-info-container menu-item"> <div class="version-info-container menu-item">
<div class="version-info-now" (click)="$event.stopPropagation()"> <div class="version-info-now" (click)="$event.stopPropagation()">
<span class="version-info-item"> <span
class="version-info-item"
(auxclick)="isShowClearBtn = !isShowClearBtn"
>
{{ 'information.installedVersion' | translate }}:<span {{ 'information.installedVersion' | translate }}:<span
class="info-content" class="info-content"
>{{ appVersion }}</span >{{ appVersion }}</span
@ -491,5 +503,14 @@
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="isShowClearBtn" class="version-info-container menu-item">
<button
mat-flat-button
class="mat-primary"
(click)="onClickClearSettingAndLogout($event)"
>
Clear setting & Logout
</button>
</div>
</ng-template> </ng-template>
</mat-menu> </mat-menu>

View File

@ -95,7 +95,8 @@
&.dsp { &.dsp {
text-indent: 0; text-indent: 0;
} }
&.sms { &.sms,
&.erp {
text-indent: 0; text-indent: 0;
letter-spacing: -1px; letter-spacing: -1px;
} }
@ -110,6 +111,9 @@
font-size: 11px; font-size: 11px;
letter-spacing: -1px; letter-spacing: -1px;
} }
&.elephant {
background-position: 50% 90%;
}
} }
&.app-layout-native-title-bar-logout, &.app-layout-native-title-bar-logout,
@ -212,7 +216,7 @@
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-video'%3E%3Cpolygon points='23 7 16 12 23 17 23 7'%3E%3C/polygon%3E%3Crect x='1' y='5' width='15' height='14' rx='2' ry='2'%3E%3C/rect%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-video'%3E%3Cpolygon points='23 7 16 12 23 17 23 7'%3E%3C/polygon%3E%3Crect x='1' y='5' width='15' height='14' rx='2' ry='2'%3E%3C/rect%3E%3C/svg%3E");
} }
.elephant { .elephant {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='#{$color}' stroke='none' xml:space='preserve'%3E%3Cpath d='M19.1,5c-1,0-2,0.4-2.7,1.1c-1.2-1-2.9-1.5-4.7-1.5c-1.7,0-3.3,0.5-4.5,1.4C6.6,5.3,5.7,5,4.8,5C2.6,5,0.9,6.7,0.9,8.8 c0,1.1,1.1,4.6,2.7,5.7c0.3,0.2,0.7,0.3,1.1,0.3c0.2,0,0.4,0,0.6-0.1c0.5-0.2,0.9-0.4,1.3-0.6c0.6,0.6,1.3,1,2.1,1.4l0.8,1.2 c0.3,0.5,0.5,1.1,0.6,1.7H9.2c-0.7,0-1.2,0.6-1.2,1.2s0.6,1.2,1.2,1.2h0.4c2.5,0,4.6-1.7,5.2-4.1l0.4-1.4c0.7-0.4,1.4-0.8,1.9-1.3 c0.4,0.3,0.9,0.5,1.5,0.8c0.2,0.1,0.4,0.1,0.6,0.1c0.4,0,0.7-0.1,1.1-0.3c1.6-1.1,2.7-4.6,2.7-5.7C22.9,6.7,21.2,5,19.1,5z M5,14.1 c-0.3,0.1-0.7,0.1-1-0.2C2.7,13,1.7,9.8,1.7,8.8c0-1.7,1.4-3.1,3.1-3.1c0.7,0,1.4,0.3,2,0.7C6.4,6.8,6.1,7.1,5.8,7.5 C5.5,7.3,5.1,7.2,4.8,7.2c-1.2,0-2.1,1-2.1,2.1c0,0.6,0.6,2.5,1.5,3.1c0.2,0.1,0.4,0.2,0.6,0.2c0.1,0,0.3,0,0.4-0.1 c0.1,0,0.2-0.1,0.3-0.1c0.2,0.4,0.4,0.8,0.7,1.1C5.8,13.8,5.4,13.9,5,14.1z M5.1,11.8c-0.1,0-0.2,0.1-0.3,0.1 c-0.1,0-0.2,0.1-0.4-0.1c-0.6-0.4-1.2-2-1.2-2.5c0-0.8,0.6-1.4,1.4-1.4C5,7.9,5.2,8,5.4,8.1c-0.3,0.7-0.5,1.4-0.5,2.2 C4.9,10.8,5,11.3,5.1,11.8z M14.7,14.8c-0.1,0-0.2,0.1-0.2,0.2l-0.4,1.6c-0.5,2.1-2.4,3.5-4.5,3.5H9.2c-0.3,0-0.5-0.2-0.5-0.5 c0-0.3,0.2-0.5,0.5-0.5h1.1c0.3,0,0.5-0.2,0.5-0.5c0-0.8-0.2-1.6-0.7-2.3L9.3,15c0-0.1-0.1-0.1-0.2-0.1c-0.8-0.3-1.5-0.8-2.1-1.3 c0,0,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.4-0.4-0.7-0.9-0.9-1.4c0,0,0-0.1,0-0.1c-0.2-0.5-0.3-1-0.3-1.5c0-2.8,2.7-5,6.1-5 s6.1,2.3,6.1,5C17.9,12.2,16.7,13.9,14.7,14.8z M18.2,8.3c0.3-0.2,0.6-0.4,0.9-0.4c0.8,0,1.4,0.6,1.4,1.4c0,0.5-0.5,2.1-1.2,2.5 C19.2,12,19.1,12,19,11.9c-0.2-0.1-0.4-0.2-0.6-0.3c0.1-0.4,0.2-0.9,0.2-1.3C18.6,9.6,18.5,8.9,18.2,8.3z M19.8,13.9 c-0.3,0.2-0.7,0.3-1,0.2c-0.5-0.2-0.9-0.4-1.3-0.7c0.3-0.3,0.5-0.7,0.7-1.1c0.2,0.1,0.4,0.2,0.6,0.3c0.1,0.1,0.3,0.1,0.4,0.1 c0.2,0,0.4-0.1,0.6-0.2c0.9-0.6,1.5-2.5,1.5-3.1c0-1.2-1-2.1-2.1-2.1c-0.5,0-0.9,0.2-1.3,0.4c-0.2-0.4-0.5-0.7-0.9-1.1 c0.6-0.6,1.3-0.9,2.1-0.9c1.7,0,3.1,1.4,3.1,3.1C22.2,9.8,21.1,13,19.8,13.9z'/%3E%3Cpath d='M11.5,16.5h1c0.3,0,0.5-0.2,0.5-0.5s-0.2-0.5-0.5-0.5h-1c-0.3,0-0.5,0.2-0.5,0.5S11.2,16.5,11.5,16.5z'/%3E%3Cpath d='M13,17.6H11c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5H13c0.3,0,0.5-0.2,0.5-0.5S13.3,17.6,13,17.6z'/%3E%3Cpath d='M17,12.4c0,0.5-0.4,0.8-0.8,0.8c-0.5,0-0.8-0.4-0.8-0.8s0.4-0.8,0.8-0.8C16.7,11.6,17,12,17,12.4z'/%3E%3Cpath d='M8.5,12.4c0,0.5-0.4,0.8-0.8,0.8c-0.5,0-0.8-0.4-0.8-0.8s0.4-0.8,0.8-0.8C8.1,11.6,8.5,12,8.5,12.4z'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ctitle%3Eelephant%3C/title%3E%3Cpath d='M24,14a17.76,17.76,0,0,0-.54-3.76c-.09-.34-.18-.65-.27-.91s-.08-.23-.13-.35-.09-.23-.14-.34a10.08,10.08,0,0,0-.69-1.3A10.22,10.22,0,0,0,20.86,5.6c-.17-.17-.35-.33-.53-.49l-.57-.46a9.45,9.45,0,0,0-1.56-.93,2.52,2.52,0,0,0-.34-.15,7.07,7.07,0,0,0-.69-.27A9.49,9.49,0,0,0,4.81,10.89V11s0,.06,0,.09h0a.36.36,0,0,1,0,.1h0s0,.06,0,.09h0l0,.08h0l0,.07h0l0,0h0l0,0h0l0,0A1.18,1.18,0,0,1,4,11a1.56,1.56,0,0,1-.11-1A3.62,3.62,0,0,1,4.24,9L1,7.57H1A5.89,5.89,0,0,0,.25,9.09c0,.13-.07.26-.1.39A5.12,5.12,0,0,0,0,10.56,4.3,4.3,0,0,0,.67,13l.22.32,0,.07.23.28,0,0a2.61,2.61,0,0,0,.23.24l.05.05.25.22,0,0,.23.17.06,0,.25.16,0,0a1.27,1.27,0,0,0,.23.12l0,0,.25.11.05,0,.22.09h.05l.24.07.05,0,.21.05h0l.22,0H4l.2,0h0l.2,0h.8l.11.37c.07.17.13.33.2.49l.23.48a9.69,9.69,0,0,0,.82,1.29l.33.42.33.37A10,10,0,0,0,8.49,19.8c.22.17.46.34.7.49a11.25,11.25,0,0,0,1.53.82.48.48,0,0,0,.14.05H11l.11,0h0l.09,0h0A.16.16,0,0,0,11.3,21v0a.2.2,0,0,0,0-.08v0a.29.29,0,0,0,0-.09h0s0,0,0-.08h0v0h0v-.88a2.87,2.87,0,0,1,2.87-2.86h0a3,3,0,0,1,.85.12l.26.1a2.62,2.62,0,0,1,.49.27l.22.16a2.85,2.85,0,0,1,1,2.21v.68h0v.37l0,.06h0a.1.1,0,0,0,0,.05v0l0,0v0l0,0,0,0h.19l.08,0a9.24,9.24,0,0,0,1.41-.7,7.83,7.83,0,0,0,.67-.45,7,7,0,0,0,.92-.8,2.61,2.61,0,0,0,.27-.3,4.94,4.94,0,0,0,.49-.64l.22-.35a5.07,5.07,0,0,0,.34-.75c.05-.13.09-.26.13-.4l.13.55a2,2,0,0,1,0,.24v.07l0,.16v.08l0,.16V18l0,.17v0c0,.06,0,.11,0,.17v.06s0,.08,0,.12v.06s0,.08,0,.11v.05l0,.14v0l0,.1v0a.14.14,0,0,1,0,.06l0,0a.08.08,0,0,0,0,.05l0,0s0,0,0,0h.07l0,0h0l.05-.05,0,0a.86.86,0,0,0,.07-.1A8.15,8.15,0,0,0,24,15Q24,14.5,24,14ZM9,9.07A.73.73,0,0,1,9,7.61.73.73,0,1,1,9,9.07Zm8,4.28a3.66,3.66,0,0,1-2.35,1.28,1.88,1.88,0,0,1-1.41-.72A5.81,5.81,0,0,1,12,10.21c.05-.9.54-3.07,4-3.77a2.18,2.18,0,0,1,.44,0h0a2.46,2.46,0,0,1,2.21,1.75C19.21,9.79,18.59,11.73,17,13.35Z' transform='translate(0 -2.82)' style='fill:%23fff'/%3E%3C/svg%3E");
} }
.mail { .mail {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'%3E%3C/path%3E%3Cpolyline points='22,6 12,13 2,6'%3E%3C/polyline%3E%3C/svg%3E"); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='#{$color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'%3E%3C/path%3E%3Cpolyline points='22,6 12,13 2,6'%3E%3C/polyline%3E%3C/svg%3E");

View File

@ -34,7 +34,10 @@ import {
KEY_LOGIN_INFO, KEY_LOGIN_INFO,
KEY_VER_INFO, KEY_VER_INFO,
EnvironmentsInfo, EnvironmentsInfo,
KEY_ENVIRONMENTS_INFO KEY_ENVIRONMENTS_INFO,
KEY_LOGIN_RES_INFO,
KEY_LOGOUT_INFO,
KEY_AUTH_INFO
} from '@app/types'; } from '@app/types';
import { import {
WebLink, WebLink,
@ -62,7 +65,12 @@ import {
ProfileDialogResult, ProfileDialogResult,
ProfileDialogData ProfileDialogData
} from '@app/layouts/messenger/dialogs/profile/profile.dialog.component'; } from '@app/layouts/messenger/dialogs/profile/profile.dialog.component';
import { DialogService } from '@ucap-webmessenger/ui'; import {
DialogService,
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap-webmessenger/ui';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { MatMenu, MatMenuTrigger } from '@angular/material/menu'; import { MatMenu, MatMenuTrigger } from '@angular/material/menu';
import { StatusCode, StatusType, WindowUtil } from '@ucap-webmessenger/core'; import { StatusCode, StatusType, WindowUtil } from '@ucap-webmessenger/core';
@ -110,6 +118,8 @@ export class TopBarComponent implements OnInit, OnDestroy {
webLinkBadgeMail = 0; webLinkBadgeMail = 0;
webLinkBadgePayment = 0; webLinkBadgePayment = 0;
webLinkbadgeEmailCountInterval: any;
appVersion: string; appVersion: string;
WebLinkType = WebLinkType; WebLinkType = WebLinkType;
@ -215,6 +225,10 @@ export class TopBarComponent implements OnInit, OnDestroy {
this.zoomSubscription.unsubscribe(); this.zoomSubscription.unsubscribe();
this.zoomSubscription = undefined; this.zoomSubscription = undefined;
} }
if (!!this.webLinkbadgeEmailCountInterval) {
clearInterval(this.webLinkbadgeEmailCountInterval);
}
} }
initWebLink(loginRes: LoginResponse): void { initWebLink(loginRes: LoginResponse): void {
@ -246,9 +260,9 @@ export class TopBarComponent implements OnInit, OnDestroy {
); );
const WebLinkMailCnt = link[0]; const WebLinkMailCnt = link[0];
const loginPw = appUserInfo.loginPw; const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw; const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId; const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = loginRes.tokenString; const token = loginRes.tokenString;
const url = WebLinkMailCnt.url const url = WebLinkMailCnt.url
@ -264,6 +278,19 @@ export class TopBarComponent implements OnInit, OnDestroy {
catchError(error => of(this.logger.log(error))) catchError(error => of(this.logger.log(error)))
) )
.subscribe(); .subscribe();
// interval
if (!this.webLinkbadgeEmailCountInterval) {
this.webLinkbadgeEmailCountInterval = setInterval(() => {
this.daesangApiService
.retrieveMailCount(url)
.pipe(
take(1),
map(res => (this.webLinkBadgeMail = res.count)),
catchError(error => of(this.logger.log(error)))
)
.subscribe();
}, 5 * 60 * 1000);
}
} }
} }
if (urlInfo.webLinkAllowedList.indexOf(WebLinkType.Payment) > -1) { if (urlInfo.webLinkAllowedList.indexOf(WebLinkType.Payment) > -1) {
@ -278,9 +305,9 @@ export class TopBarComponent implements OnInit, OnDestroy {
); );
const WebLinkPaymentCnt = link[0]; const WebLinkPaymentCnt = link[0];
const loginPw = appUserInfo.loginPw; const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw; const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId; const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = loginRes.tokenString; const token = loginRes.tokenString;
const url = WebLinkPaymentCnt.url const url = WebLinkPaymentCnt.url
@ -406,65 +433,74 @@ export class TopBarComponent implements OnInit, OnDestroy {
environment.customConfig.appKey environment.customConfig.appKey
); );
const loginPw = appUserInfo.loginPw; const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw; const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId; const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = this.loginRes.tokenString; const token = this.loginRes.tokenString;
const erpPw = this.daesangCipherService.encryptForSapErp(
'aes256-daesang-key!!',
this.loginRes.userInfo.employeeNum
);
const url = link.url const url = link.url
.replace(/(\(%USER_TOKEN%\))/g, token) .replace(/(\(%USER_TOKEN%\))/g, token)
.replace(/(\(%USER_ID%\))/g, loginId) .replace(/(\(%USER_ID%\))/g, loginId)
.replace(/(\(%USER_PASS%\))/g, loginPw); .replace(/(\(%USER_PASS%\))/g, loginPw)
.replace(/(\(%ENC_PASSWD%\))/g, erpPw);
let width = 1024; let width = 1024;
let height = 768; let height = 768;
let openType = 'INNER-POPUP'; let openType = 'INNER-POPUP';
switch (link.key) { switch (link.key) {
case WebLinkType.Sms: // /** SMS URL */
/** SMS URL */ // case WebLinkType.Sms:
{ // {
width = 685; // width = 685;
height = 640; // height = 640;
} // }
break; // break;
// case WebLinkType.Itsvcdesk:
// /** IT서비스데스크 URL */ // /** IT서비스데스크 URL */
// case WebLinkType.Itsvcdesk:
// { // {
// width = 1400; // width = 1400;
// height = 1000; // height = 1000;
// } // }
// break; // break;
case WebLinkType.Conf:
/** 화상회의 URL */ /** 화상회의 URL */
case WebLinkType.Conf:
{ {
} }
break; break;
case WebLinkType.Itsvcdesk: /** SMS URL */
case WebLinkType.Sms:
/** IT서비스데스크 URL */ /** IT서비스데스크 URL */
case WebLinkType.Dsp: case WebLinkType.Itsvcdesk:
/** DSP URL */ /** DSP URL */
case WebLinkType.Webhard: case WebLinkType.Dsp:
/** 웹하드 URL */ /** 웹하드 URL */
case WebLinkType.Ep: case WebLinkType.Webhard:
/** EP URL */ /** EP URL */
case WebLinkType.Sop: case WebLinkType.Ep:
/** S&OP회의 URL */ /** S&OP회의 URL */
case WebLinkType.Som: case WebLinkType.Sop:
/** S&OM회의 URL */ /** S&OM회의 URL */
case WebLinkType.Elephant: case WebLinkType.Som:
/** 코끼리 URL */ /** 코끼리 URL */
case WebLinkType.UrgntNews: case WebLinkType.Elephant:
/** 개인속보 URL */ /** 개인속보 URL */
case WebLinkType.MailCnt: case WebLinkType.UrgntNews:
/** 메일Count URL */ /** 메일Count URL */
case WebLinkType.Mail: case WebLinkType.MailCnt:
/** 메일 링크 URL */ /** 메일 링크 URL */
case WebLinkType.PaymentCnt: case WebLinkType.Mail:
/** 결재Count URL */ /** 결재Count URL */
case WebLinkType.Payment: case WebLinkType.PaymentCnt:
/** 결재링크 URL */ /** 결재링크 URL */
case WebLinkType.ChgPassword: case WebLinkType.Payment:
/** Erp URL */
case WebLinkType.Erp:
/** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */ /** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */
case WebLinkType.ChgPassword:
{ {
openType = 'DEFAULT-BROWSER'; openType = 'DEFAULT-BROWSER';
} }
@ -692,4 +728,31 @@ export class TopBarComponent implements OnInit, OnDestroy {
matDialogRef.removePanelClass('hideDialog'); matDialogRef.removePanelClass('hideDialog');
} }
} }
async onClickClearSettingAndLogout() {
const result = await this.dialogService.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
width: '400px',
data: {
title: 'Clear & Logout?',
html: 'Clear General Setting And Logout?'
}
});
if (!!result && !!result.choice && result.choice) {
this.localStorageService.remove(KEY_APP_USER_INFO);
this.sessionStorageService.remove(KEY_LOGIN_RES_INFO);
this.sessionStorageService.remove(KEY_VER_INFO);
this.sessionStorageService.remove(KEY_LOGIN_INFO);
this.sessionStorageService.remove(KEY_URL_INFO);
this.sessionStorageService.remove(KEY_AUTH_INFO);
this.sessionStorageService.remove(KEY_LOGOUT_INFO);
this.nativeService.clearAppStorage();
this.dialogService.closeAll();
this.store.dispatch(AuthenticationStore.loginRedirect());
}
}
} }

View File

@ -53,15 +53,16 @@
</div> </div>
--> -->
<ucap-account-login <ucap-account-login
[companyList]="companyList$ | async" [companyList]="companyList"
[curCompanyCode]="fixedCompany" [curCompanyCode]="fixedCompany"
[notiText]="fixedNotiBtnText" [notiText]="fixedNotiBtnText"
[loginBtnEnable]="loginBtnEnable" [loginBtnEnable]="loginBtnEnable"
[loginBtnText]="loginBtnText" [loginBtnText]="loginBtnText"
[companyCode]="appUserInfo?.companyCode" [companyCode]="appUserInfo?.companyCode"
[loginId]="appUserInfo?.loginId" [loginId]="appUserInfo?.loginId"
[loginPw]="loginPw"
[rememberMe]="appUserInfo?.rememberMe" [rememberMe]="appUserInfo?.rememberMe"
[autoLogin]="appUserInfo?.settings?.general?.autoLogin" [autoLogin]="autologin"
[useRememberMe]="useRememberMe" [useRememberMe]="useRememberMe"
[useAutoLogin]="useAutoLogin" [useAutoLogin]="useAutoLogin"
(login)="onLogin($event)" (login)="onLogin($event)"

View File

@ -1,15 +1,19 @@
import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { Component, OnInit, OnDestroy, Inject, NgZone } from '@angular/core';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { Company } from '@ucap-webmessenger/api-external'; import {
Company,
ExternalApiService,
CompanyListRequest
} from '@ucap-webmessenger/api-external';
import { ServerErrorCode, ProtocolService } from '@ucap-webmessenger/protocol'; import { ServerErrorCode, ProtocolService } from '@ucap-webmessenger/protocol';
import * as AppStore from '@app/store'; import * as AppStore from '@app/store';
import * as AuthenticationStore from '@app/store/account/authentication'; import * as AuthenticationStore from '@app/store/account/authentication';
import * as CompanyStore from '@app/store/setting/company'; import * as CompanyStore from '@app/store/setting/company';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription, of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map, take, catchError } from 'rxjs/operators';
import { import {
DialogService, DialogService,
AlertDialogComponent, AlertDialogComponent,
@ -34,6 +38,7 @@ import { AppAuthenticationService } from '@app/services/authentication.service';
import { logoutInitialize } from '@app/store/account/authentication'; import { logoutInitialize } from '@app/store/account/authentication';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native'; import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
import { StatusCode } from '@ucap-webmessenger/api';
@Component({ @Component({
selector: 'app-page-account-login', selector: 'app-page-account-login',
@ -44,7 +49,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {
fixedCompany: string; fixedCompany: string;
fixedNotiBtnText: string; fixedNotiBtnText: string;
companyList$: Observable<Company[]>; companyList: Company[];
companyListSubscription: Subscription;
loginFailureCount: Subscription; loginFailureCount: Subscription;
@ -57,6 +63,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {
waitingTime: number; waitingTime: number;
appUserInfo: AppUserInfo; appUserInfo: AppUserInfo;
loginPw: string;
autologin: boolean;
useRememberMe: boolean; useRememberMe: boolean;
useAutoLogin: boolean; useAutoLogin: boolean;
@ -85,7 +93,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
private protocolService: ProtocolService, private protocolService: ProtocolService,
private localStorageService: LocalStorageService, private localStorageService: LocalStorageService,
private sessionStorageService: SessionStorageService, private sessionStorageService: SessionStorageService,
private appAuthenticationService: AppAuthenticationService private externalApiService: ExternalApiService,
private appAuthenticationService: AppAuthenticationService,
private ngZone: NgZone
) { ) {
this.useRememberMe = this.useRememberMe =
environment.productConfig.authentication.rememberMe.use; environment.productConfig.authentication.rememberMe.use;
@ -95,6 +105,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
KEY_APP_USER_INFO, KEY_APP_USER_INFO,
environment.customConfig.appKey environment.customConfig.appKey
); );
if (!!this.appUserInfo) {
this.autologin = this.appUserInfo.settings.general.autoLogin || false;
}
this.rotateInfomationIndex = this.rotateInfomationIndex =
new Date().getTime() % this.rotateInfomation.length; new Date().getTime() % this.rotateInfomation.length;
@ -113,15 +126,36 @@ export class LoginPageComponent implements OnInit, OnDestroy {
this.defatulLoginBtnText = this.translateService.instant('accounts.login'); this.defatulLoginBtnText = this.translateService.instant('accounts.login');
this.defatulWaitingTime = 5 * 60; // sec this.defatulWaitingTime = 5 * 60; // sec
this.store.dispatch( this.externalApiService
CompanyStore.companyList({ .companyList({
companyGroupCode: environment.companyConfig.companyGroupCode companyGroupCode: environment.companyConfig.companyGroupCode
} as CompanyListRequest)
.pipe(
take(1),
map(res => {
if (res.statusCode === StatusCode.Success) {
this.store.dispatch(CompanyStore.companyListSuccess(res));
} else {
this.store.dispatch(
CompanyStore.companyListFailure({ error: 'Failed' })
);
}
}),
catchError(error => {
console.log('network disconnected', error);
return of();
}) })
); )
.subscribe();
this.companyList$ = this.store.pipe( this.companyListSubscription = this.store
select(AppStore.SettingSelector.CompanySelector.companyList) .pipe(
); select(AppStore.SettingSelector.CompanySelector.companyList),
map(companyList => {
this.companyList = companyList;
})
)
.subscribe();
this.loginFailureCount = this.store this.loginFailureCount = this.store
.pipe( .pipe(
@ -189,6 +223,56 @@ export class LoginPageComponent implements OnInit, OnDestroy {
this.nativeService.idleStateStop(); this.nativeService.idleStateStop();
} }
/** CASE :: 자동로그인 실패 (비밀번호 변경에 따른 실패, 네트워크 절체) */
if (!!personLogout && !!personLogout.autoLogin) {
switch (personLogout.autoLogin.state) {
case 'IDPW_FAIL':
{
this.ngZone.run(() => {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
width: '360px',
data: {
title: '',
html: this.translateService.instant(
'accounts.errors.loginFailedIdPw'
)
}
});
});
}
break;
case 'NETWORK_FAIL':
{
this.loginPw = this.appUserInfo.loginPw;
this.autologin = true;
this.ngZone.run(() => {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
width: '360px',
data: {
title: this.translateService.instant(
'accounts.errors.loginFailed'
),
html: this.translateService.instant(
'accounts.errors.networkFailedAndRetry'
)
}
});
});
}
break;
}
}
/** CASE :: 중복 로그인, Remote 로그아웃 */
if (!!personLogout && !!personLogout.reasonCode) { if (!!personLogout && !!personLogout.reasonCode) {
let msg = this.translateService.instant('accounts.results.doLogout'); let msg = this.translateService.instant('accounts.results.doLogout');
switch (personLogout.reasonCode) { switch (personLogout.reasonCode) {
@ -234,6 +318,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.companyListSubscription) {
this.companyListSubscription.unsubscribe();
}
if (!!this.loginFailureCount) { if (!!this.loginFailureCount) {
this.loginFailureCount.unsubscribe(); this.loginFailureCount.unsubscribe();
} }
@ -268,11 +355,70 @@ export class LoginPageComponent implements OnInit, OnDestroy {
autoLogin: boolean; autoLogin: boolean;
notValid: () => void; notValid: () => void;
}) { }) {
this.sessionStorageService.remove(KEY_LOGOUT_INFO);
this.loginBtnEnable = false; this.loginBtnEnable = false;
setTimeout(() => { setTimeout(() => {
this.loginBtnEnable = true; this.loginBtnEnable = true;
}, 30 * 1000); }, 30 * 1000);
if (!this.companyList) {
this.externalApiService
.companyList({
companyGroupCode: environment.companyConfig.companyGroupCode
} as CompanyListRequest)
.pipe(
take(1),
map(res => {
if (res.statusCode === StatusCode.Success) {
this.store.dispatch(CompanyStore.companyListSuccess(res));
// Recursion function > onLogin
this.onLogin(value);
} else {
this.ngZone.run(() => {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
width: '360px',
data: {
title: this.translateService.instant(
'accounts.errors.loginFailed'
),
html: this.translateService.instant(
'accounts.errors.networkFailedAndRetry'
)
}
});
this.loginBtnEnable = true;
});
}
}),
catchError(error => {
this.ngZone.run(() => {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
width: '360px',
data: {
title: this.translateService.instant(
'accounts.errors.loginFailed'
),
html: this.translateService.instant(
'accounts.errors.networkFailedAndRetry'
)
}
});
this.loginBtnEnable = true;
});
return of();
})
)
.subscribe();
} else {
this.store.dispatch( this.store.dispatch(
AuthenticationStore.webLogin({ AuthenticationStore.webLogin({
loginInfo: { loginInfo: {
@ -286,6 +432,7 @@ export class LoginPageComponent implements OnInit, OnDestroy {
}) })
); );
} }
}
onClickNoti() { onClickNoti() {
// For Daesang,, // For Daesang,,

View File

@ -79,6 +79,12 @@ export class AppAuthenticationService {
} }
} }
}; };
if (!!environment.productConfig.defaultSettings.general.autoLaunch) {
this.nativeService.changeAutoLaunch(
environment.productConfig.defaultSettings.general.autoLaunch
);
}
} }
appUserInfo = { appUserInfo = {
@ -118,6 +124,7 @@ export class AppAuthenticationService {
environment.customConfig.appKey environment.customConfig.appKey
); );
if (!!appUserInfo) {
appUserInfo = { appUserInfo = {
...appUserInfo, ...appUserInfo,
settings: { settings: {
@ -136,3 +143,4 @@ export class AppAuthenticationService {
); );
} }
} }
}

View File

@ -1,7 +1,6 @@
import { delGroupSuccess, buddy2 } from './../store/messenger/sync/actions';
import { Injectable, Inject } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { tap, withLatestFrom, take } from 'rxjs/operators'; import { tap, withLatestFrom } from 'rxjs/operators';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
@ -95,7 +94,11 @@ import {
NotificationType, NotificationType,
WindowState WindowState
} from '@ucap-webmessenger/native'; } from '@ucap-webmessenger/native';
import { StringUtil, DialogService } from '@ucap-webmessenger/ui'; import {
StringUtil,
DialogService,
TranslateService as UcapTranslateService
} from '@ucap-webmessenger/ui';
import { import {
UmgProtocolService, UmgProtocolService,
SSVC_TYPE_UMG_NOTI, SSVC_TYPE_UMG_NOTI,
@ -110,10 +113,10 @@ import {
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type'; import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { NotificationMethod } from '@ucap-webmessenger/core'; import { NotificationMethod, LocaleCode } from '@ucap-webmessenger/core';
import { Dictionary } from '@ngrx/entity'; import { Dictionary } from '@ngrx/entity';
import { MessageType } from '@ucap-webmessenger/api-message'; import { MessageType } from '@ucap-webmessenger/api-message';
import { LogoutInfo, KEY_LOGOUT_INFO } from '@app/types'; import { LogoutInfo, KEY_LOGOUT_INFO, KEY_VER_INFO } from '@app/types';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deleteMessageSuccess } from '@app/store/messenger/message'; import { deleteMessageSuccess } from '@app/store/messenger/message';
import { ServerErrorCode } from '@ucap-webmessenger/protocol'; import { ServerErrorCode } from '@ucap-webmessenger/protocol';
@ -130,6 +133,11 @@ import {
ChatSetting ChatSetting
} from '@ucap-webmessenger/ui-settings'; } from '@ucap-webmessenger/ui-settings';
import clone from 'clone'; import clone from 'clone';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
import { UserInfo, RoomUserData } from '@ucap-webmessenger/protocol-sync';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { QueryProtocolService } from '@ucap-webmessenger/protocol-query';
import { UserInfoListState } from '@app/store/messenger/room';
@Injectable() @Injectable()
export class AppNotificationService { export class AppNotificationService {
@ -140,8 +148,10 @@ export class AppNotificationService {
private roomProtocolService: RoomProtocolService, private roomProtocolService: RoomProtocolService,
private groupProtocolService: GroupProtocolService, private groupProtocolService: GroupProtocolService,
private buddyProtocolService: BuddyProtocolService, private buddyProtocolService: BuddyProtocolService,
private queryProtocolService: QueryProtocolService,
private statusProtocolService: StatusProtocolService, private statusProtocolService: StatusProtocolService,
private translateService: TranslateService, private translateService: TranslateService,
private ucapTranslateService: UcapTranslateService,
private optionProtocolService: OptionProtocolService, private optionProtocolService: OptionProtocolService,
private umgProtocolService: UmgProtocolService, private umgProtocolService: UmgProtocolService,
private localStorageService: LocalStorageService, private localStorageService: LocalStorageService,
@ -204,14 +214,44 @@ export class AppNotificationService {
this.store.pipe( this.store.pipe(
select((state: any) => state.messenger.room.roomInfo as RoomInfo) select((state: any) => state.messenger.room.roomInfo as RoomInfo)
), ),
this.store.pipe(
select(
(state: any) =>
state.messenger.room.userInfoList.entities as Dictionary<
UserInfoListState
>
)
),
this.store.pipe( this.store.pipe(
select( select(
(state: any) => (state: any) =>
state.messenger.sync.room.entities as Dictionary<RoomInfo> state.messenger.sync.room.entities as Dictionary<RoomInfo>
) )
),
this.store.pipe(
select(
(state: any) =>
state.messenger.sync.buddy2.entities as Dictionary<UserInfo>
)
),
this.store.pipe(
select(
(state: any) =>
state.messenger.sync.roomUserShort.entities as Dictionary<
RoomUserData
>
)
) )
), ),
tap(([notiOrRes, curRoomInfo, roomList]) => { tap(
([
notiOrRes,
curRoomInfo,
curRoomUserInfo,
roomList,
buddyList,
roomUserShorts
]) => {
switch (notiOrRes.SSVC_TYPE) { switch (notiOrRes.SSVC_TYPE) {
case SSVC_TYPE_EVENT_SEND_RES: case SSVC_TYPE_EVENT_SEND_RES:
case SSVC_TYPE_EVENT_SEND_NOTI: case SSVC_TYPE_EVENT_SEND_NOTI:
@ -232,14 +272,6 @@ export class AppNotificationService {
if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) { if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) {
let doNoti = true; let doNoti = true;
// 방별 알림이 꺼져 있으면 노티 안함.
if (
!!roomList[noti.roomSeq] &&
!roomList[noti.roomSeq].receiveAlarm
) {
doNoti = false;
}
const windowState = this.nativeService.getWindowState(); const windowState = this.nativeService.getWindowState();
// 현재 열려 있는 방일경우 노티 안함. // 현재 열려 있는 방일경우 노티 안함.
@ -248,8 +280,27 @@ export class AppNotificationService {
!!curRoomInfo.roomSeq && !!curRoomInfo.roomSeq &&
curRoomInfo.roomSeq === noti.roomSeq && curRoomInfo.roomSeq === noti.roomSeq &&
!!windowState && !!windowState &&
windowState !== WindowState.Minimized && windowState.windowState !== WindowState.Minimized &&
windowState !== WindowState.Hidden windowState.windowState !== WindowState.Hidden
) {
doNoti = false;
}
// // 포커스 아웃일때 무조건 노티.
// // Case 1 : 단순 포커스 아웃.
// // Case 2 : hidden 시 포커스 인 상태이지만 위에서 필터링 됨.
// console.log(windowState);
// if (
// windowState.windowFocusState !==
// ElectronBrowserWindowChannel.Focus
// ) {
// doNoti = true;
// }
// 방별 알림이 꺼져 있으면 노티 안함. > 우선순위 최상위.
if (
!!roomList[noti.roomSeq] &&
!roomList[noti.roomSeq].receiveAlarm
) { ) {
doNoti = false; doNoti = false;
} }
@ -296,6 +347,76 @@ export class AppNotificationService {
appUserInfo.settings.notification appUserInfo.settings.notification
.alertExposureTime * 1000 .alertExposureTime * 1000
}; };
// Sender Info setting
// STEP 1 >> In buddy group.
let senderInfo: any = buddyList[noti.SENDER_SEQ];
// STEP 2 >> In Current Room Users.
if (!senderInfo) {
senderInfo = curRoomUserInfo[noti.SENDER_SEQ];
}
// STEP 3 >> In All Room Users.
if (!senderInfo) {
for (const key in roomUserShorts) {
if (key === undefined) {
continue;
}
if (roomUserShorts.hasOwnProperty(key)) {
const element = roomUserShorts[key];
const filteredUserInfos = element.userInfos.filter(
info => info.seq === noti.SENDER_SEQ
);
if (
!!filteredUserInfos &&
filteredUserInfos.length > 0
) {
senderInfo = filteredUserInfos[0];
break;
}
}
}
}
// Sender Info setting.
if (!!senderInfo) {
// name set
let name = senderInfo.name;
let grade = senderInfo.grade;
switch (
this.ucapTranslateService.currentLang.toUpperCase()
) {
case LocaleCode.English:
name = senderInfo.nameEn;
grade = senderInfo.gradeEn;
break;
case LocaleCode.Chinese:
name = senderInfo.nameCn;
grade = senderInfo.gradeCn;
break;
}
notiReq.title = this.translateService.instant(
'notification.titleChatEventArrivedByUser',
{
userInfo: !!grade ? `${name} ${grade}` : name
}
);
// Image set.
if (!!senderInfo.profileImageFile) {
const sessionVerinfo = this.sessionStorageService.get<
VersionInfo2Response
>(KEY_VER_INFO);
notiReq.image = `${sessionVerinfo.profileRoot}${senderInfo.profileImageFile}`;
}
}
// express noti popup
this.nativeService.notify(notiReq); this.nativeService.notify(notiReq);
} }
} }
@ -347,7 +468,8 @@ export class AppNotificationService {
default: default:
break; break;
} }
}) }
)
) )
.subscribe(); .subscribe();
this.infoProtocolService.notification$ this.infoProtocolService.notification$

View File

@ -76,7 +76,11 @@ import {
ServiceProtocolService, ServiceProtocolService,
UserPasswordSetResponse UserPasswordSetResponse
} from '@ucap-webmessenger/protocol-service'; } from '@ucap-webmessenger/protocol-service';
import { DaesangUrlInfoResponse } from '@ucap-webmessenger/api-external'; import {
DaesangUrlInfoResponse,
ExternalApiService,
CompanyListRequest
} from '@ucap-webmessenger/api-external';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type'; import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { DaesangCipherService, WebLinkType } from '@ucap-webmessenger/daesang'; import { DaesangCipherService, WebLinkType } from '@ucap-webmessenger/daesang';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -84,10 +88,15 @@ import {
InfoProtocolService, InfoProtocolService,
UserResponse UserResponse
} from '@ucap-webmessenger/protocol-info'; } from '@ucap-webmessenger/protocol-info';
import { StatusCode } from '@ucap-webmessenger/api';
@Injectable() @Injectable()
export class Effects { export class Effects {
webLogin$ = createEffect(() => retryCount = 0;
retryInterval = 3000; // ms
maxRetryCount = (1 * 60 * 1000) / this.retryInterval; // 20 count due to 1 min.
webLogin$ = createEffect(
() =>
this.actions$.pipe( this.actions$.pipe(
ofType(webLogin), ofType(webLogin),
map(action => action), map(action => action),
@ -96,8 +105,10 @@ export class Effects {
loginInfo: LoginInfo; loginInfo: LoginInfo;
rememberMe: boolean; rememberMe: boolean;
autoLogin: boolean; autoLogin: boolean;
}) => }) => {
this.piService const selfParam = params;
return this.piService
.login2({ .login2({
loginId: params.loginInfo.loginId, loginId: params.loginInfo.loginId,
loginPw: params.loginInfo.loginPw, loginPw: params.loginInfo.loginPw,
@ -106,22 +117,101 @@ export class Effects {
.pipe( .pipe(
map((res: Login2Response) => { map((res: Login2Response) => {
if ('success' !== res.status.toLowerCase()) { if ('success' !== res.status.toLowerCase()) {
if (!!params.autoLogin) {
// auto login Failure.
// clear setting for autologin.
// this.localStorageService.remove(KEY_APP_USER_INFO);
const appUserInfo = this.localStorageService.encGet<
AppUserInfo
>(KEY_APP_USER_INFO, environment.customConfig.appKey);
appUserInfo.settings.general.autoLogin = false;
this.localStorageService.encSet<AppUserInfo>(
KEY_APP_USER_INFO,
appUserInfo,
environment.customConfig.appKey
);
// Logout reason setting.
this.sessionStorageService.set<LogoutInfo>(
KEY_LOGOUT_INFO,
{
personLogout: true,
autoLogin: {
state: 'IDPW_FAIL'
}
} as LogoutInfo
);
this.router.navigateByUrl('/account/login');
} else {
this.store.dispatch(increaseLoginFailCount({})); this.store.dispatch(increaseLoginFailCount({}));
return webLoginFailure({ error: 'Failed' }); this.store.dispatch(webLoginFailure({ error: 'Failed' }));
}
} else { } else {
this.store.dispatch(initialLoginFailCount({})); this.store.dispatch(initialLoginFailCount({}));
return webLoginSuccess({ this.store.dispatch(
webLoginSuccess({
loginInfo: params.loginInfo, loginInfo: params.loginInfo,
rememberMe: params.rememberMe, rememberMe: params.rememberMe,
autoLogin: params.autoLogin, autoLogin: params.autoLogin,
login2Response: res login2Response: res
}); })
);
} }
}), }),
catchError(error => of(webLoginFailure({ error }))) catchError(error => {
) if (!!selfParam.autoLogin) {
) if (this.maxRetryCount > this.retryCount) {
this.store.dispatch(logoutInitialize());
setTimeout(() => {
// this.store.dispatch(webLogin(selfParam));
this.router.navigateByUrl('/account/login');
}, this.retryInterval);
this.retryCount++;
console.log('retry', this.retryCount, this.maxRetryCount);
return of(webLoginFailure({ error }));
} else {
console.log(
'retry End',
this.retryCount,
this.maxRetryCount
);
// clear setting for autologin.
const appUserInfo = this.localStorageService.encGet<
AppUserInfo
>(KEY_APP_USER_INFO, environment.customConfig.appKey);
appUserInfo.settings.general.autoLogin = false;
this.localStorageService.encSet<AppUserInfo>(
KEY_APP_USER_INFO,
appUserInfo,
environment.customConfig.appKey
);
// Logout reason setting.
this.sessionStorageService.set<LogoutInfo>(
KEY_LOGOUT_INFO,
{
personLogout: true,
autoLogin: {
state: 'NETWORK_FAIL'
}
} as LogoutInfo
);
}
}
this.router.navigateByUrl('/account/login');
console.log('not retry');
return of(webLoginFailure({ error }));
})
);
}
) )
),
{ dispatch: false }
); );
webLoginSuccess$ = createEffect( webLoginSuccess$ = createEffect(
@ -500,6 +590,7 @@ export class Effects {
private sessionStorageService: SessionStorageService, private sessionStorageService: SessionStorageService,
private piService: PiService, private piService: PiService,
private appAuthenticationService: AppAuthenticationService, private appAuthenticationService: AppAuthenticationService,
private externalApiService: ExternalApiService,
private protocolService: ProtocolService, private protocolService: ProtocolService,
private authenticationProtocolService: AuthenticationProtocolService, private authenticationProtocolService: AuthenticationProtocolService,
private infoProtocolService: InfoProtocolService, private infoProtocolService: InfoProtocolService,

View File

@ -925,30 +925,6 @@ export class Effects {
}) })
); );
} }
}
// not opened room :: unread count increased
if (
action.SVC_TYPE === SVC_TYPE_EVENT &&
action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES
) {
/**
* RES noti .
* unread count .
*/
} else {
if (!roomInfo || roomInfo.roomSeq !== action.roomSeq) {
if (!!trgtRoomInfos && !!trgtRoomInfos[action.roomSeq]) {
const noReadCnt = trgtRoomInfos[action.roomSeq].noReadCnt;
this.store.dispatch(
SyncStore.updateUnreadCount({
roomSeq: action.roomSeq,
noReadCnt: noReadCnt + 1
})
);
}
}
}
if (action.info.type === EventType.File) { if (action.info.type === EventType.File) {
// File 정보 수집. // File 정보 수집.
@ -962,6 +938,34 @@ export class Effects {
}) })
); );
} }
} else {
// not opened room :: unread count increased
if (
action.SVC_TYPE === SVC_TYPE_EVENT &&
action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES
) {
/**
* RES noti .
* unread count .
*/
} else {
if (
!!trgtRoomInfos &&
!!trgtRoomInfos[action.roomSeq] &&
action.info.type !== EventType.Join &&
action.info.type !== EventType.Exit &&
action.info.type !== EventType.ForcedExit
) {
const noReadCnt = trgtRoomInfos[action.roomSeq].noReadCnt;
this.store.dispatch(
SyncStore.updateUnreadCount({
roomSeq: action.roomSeq,
noReadCnt: noReadCnt + 1
})
);
}
}
}
// 대화 > 리스트 :: finalEventMessage refresh // 대화 > 리스트 :: finalEventMessage refresh
this.store.dispatch(ChatStore.newEventMessage(action)); this.store.dispatch(ChatStore.newEventMessage(action));

View File

@ -188,3 +188,8 @@ export const exitFailure = createAction(
'[Messenger::Room] Exit Failure', '[Messenger::Room] Exit Failure',
props<{ error: any }>() props<{ error: any }>()
); );
export const syncRoomRefreshByInvite = createAction(
'[Messenger::Room] Sync Room Refresh by invite',
props<InfoRequest>()
);

View File

@ -5,6 +5,8 @@ import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Store, select } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { environment } from '../../../../environments/environment';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { import {
tap, tap,
@ -68,7 +70,8 @@ import {
exitForcingFailure, exitForcingFailure,
exitForcingSuccess, exitForcingSuccess,
exitNotificationOthers, exitNotificationOthers,
clearRoomUser clearRoomUser,
syncRoomRefreshByInvite
} from './actions'; } from './actions';
import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { LoginInfo, KEY_LOGIN_INFO, KEY_VER_INFO } from '@app/types'; import { LoginInfo, KEY_LOGIN_INFO, KEY_VER_INFO } from '@app/types';
@ -79,6 +82,8 @@ import {
AlertDialogData, AlertDialogData,
AlertDialogResult AlertDialogResult
} from '@ucap-webmessenger/ui'; } from '@ucap-webmessenger/ui';
import { ActivatedRouteSnapshot } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -285,6 +290,29 @@ export class Effects {
) )
), ),
exhaustMap(([action, roomInfo]) => { exhaustMap(([action, roomInfo]) => {
if (
environment.productConfig.CommonSetting.maxChatRoomUser <
action.req.userSeqs.length
) {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.translateService.instant('chat.errors.label'),
html: this.translateService.instant(
'chat.errors.maxCountOfRoomMemberWith',
{
maxCount:
environment.productConfig.CommonSetting.maxChatRoomUser
}
)
}
});
return of(inviteFailure({ error: 'over size room users !!' }));
}
if (roomInfo.roomType === RoomType.Single) { if (roomInfo.roomType === RoomType.Single) {
// Re Open // Re Open
return this.roomProtocolService.open(action.req).pipe( return this.roomProtocolService.open(action.req).pipe(
@ -371,10 +399,11 @@ export class Effects {
) )
), ),
tap(([action, roomInfo]) => { tap(([action, roomInfo]) => {
if (!!roomInfo && roomInfo.roomSeq === action.noti.roomSeq) {
const loginInfo = this.sessionStorageService.get<LoginInfo>( const loginInfo = this.sessionStorageService.get<LoginInfo>(
KEY_LOGIN_INFO KEY_LOGIN_INFO
); );
if (!!roomInfo && roomInfo.roomSeq === action.noti.roomSeq) {
this.store.dispatch( this.store.dispatch(
info({ info({
roomSeq: action.noti.roomSeq, roomSeq: action.noti.roomSeq,
@ -383,6 +412,15 @@ export class Effects {
}) })
); );
} }
// room list refresh for exist.
this.store.dispatch(
syncRoomRefreshByInvite({
roomSeq: action.noti.roomSeq,
isDetail: true,
localeCode: loginInfo.localeCode
})
);
}) })
); );
}, },
@ -454,6 +492,7 @@ export class Effects {
private store: Store<any>, private store: Store<any>,
private roomProtocolService: RoomProtocolService, private roomProtocolService: RoomProtocolService,
private sessionStorageService: SessionStorageService, private sessionStorageService: SessionStorageService,
private translateService: TranslateService,
private dialogService: DialogService, private dialogService: DialogService,
private logger: NGXLogger private logger: NGXLogger
) {} ) {}

View File

@ -72,6 +72,7 @@ export const reducer = createReducer(
on(updateSuccess, (state, action) => { on(updateSuccess, (state, action) => {
const curRoomInfo = state.roomInfo; const curRoomInfo = state.roomInfo;
if (!curRoomInfo) { if (!curRoomInfo) {
return { ...state }; return { ...state };
} }

View File

@ -540,6 +540,62 @@ export class Effects {
}, },
{ dispatch: false } { dispatch: false }
); );
syncRoomRefreshByInvite$ = createEffect(
() => {
let roomInfo: RoomInfo;
let userInfoShortList: UserInfoShort[];
let userInfoList: RoomUserInfo[];
return this.actions$.pipe(
ofType(RoomStore.syncRoomRefreshByInvite),
tap(() => {
roomInfo = null;
userInfoShortList = [];
userInfoList = [];
}),
withLatestFrom(
this.store.pipe(
select((state: any) => state.messenger.sync.room.ids as string[])
)
),
switchMap(([req, roomSeqList]) => {
const index = roomSeqList.findIndex(
(roomSeq, i) => roomSeq === req.roomSeq
);
if (index > -1) {
return this.roomProtocolService.info(req).pipe(
map(res => {
switch (res.SSVC_TYPE) {
case SSVC_TYPE_ROOM_INFO_ROOM:
roomInfo = (res as InfoData).roomInfo;
break;
case SSVC_TYPE_ROOM_INFO_USER:
userInfoShortList.push(...(res as UserShortData).userInfos);
break;
case SSVC_TYPE_ROOM_INFO_USER2:
userInfoList.push(...(res as UserData).userInfos);
break;
case SSVC_TYPE_ROOM_INFO_RES:
this.store.dispatch(
refreshRoomSuccess({
roomInfo,
userInfoShortList,
userInfoList
})
);
break;
}
}),
catchError(error => of(refreshRoomFailure({ error })))
);
} else {
return of();
}
})
);
},
{ dispatch: false }
);
// 대화상대 초대 성공 후 처리. // 대화상대 초대 성공 후 처리.
inviteSuccess$ = createEffect(() => inviteSuccess$ = createEffect(() =>

View File

@ -105,6 +105,11 @@ export const reducer = createReducer(
on(clearRoomUsers, (state, action) => { on(clearRoomUsers, (state, action) => {
const roomInfo = state.room.entities[action.roomSeq]; const roomInfo = state.room.entities[action.roomSeq];
if (!roomInfo) {
// 방에 초대만 되고 대화가 발생하지 않아 방정보가 없을때, 방에서 강퇴된다면 roomInfo 가 없을 수 있다.
return { ...state };
}
const roomUserList: RoomUserDetailData = { const roomUserList: RoomUserDetailData = {
...state.roomUser.entities[action.roomSeq] ...state.roomUser.entities[action.roomSeq]
}; };

View File

@ -2,8 +2,17 @@ export const KEY_LOGOUT_INFO = 'ucap::LOGOUT_INFO';
export interface LogoutInfo { export interface LogoutInfo {
personLogout: boolean; personLogout: boolean;
/** 중복로그인, Remote 로그아웃 시에 사용. */
reasonCode?: number; reasonCode?: number;
ip?: string; ip?: string; // 중복로그인시 사용.
mac?: string; mac?: string; // 중복로그인시 사용.
forceType?: string; forceType?: string; // remote 로그아웃 시 사용.
/** 자동로그인 실패시. */
autoLogin?: {
state: string;
id?: string;
pw?: string;
};
} }

View File

@ -45,7 +45,9 @@
"failToChangePassword": "Failed to change password.", "failToChangePassword": "Failed to change password.",
"loginFailed": "Failed to login", "loginFailed": "Failed to login",
"loginFailedIdPw": "Username or password do not match.", "loginFailedIdPw": "Username or password do not match.",
"loginFailOverTry": "Password error count exceeded. <br/> Check your password <br/> Please try again later." "loginFailOverTry": "Password error count exceeded. <br/> Check your password <br/> Please try again later.",
"networkFailedAndExit": "Please exit the program due to a network problem. <br/> Please check the network and try again.",
"networkFailedAndRetry": "Cannot run due to network problem. <br/> Please check the network and try again."
} }
}, },
"profile": { "profile": {
@ -57,6 +59,7 @@
"removeBuddy": "Remove a buddy", "removeBuddy": "Remove a buddy",
"remoteSupport": "Remote support", "remoteSupport": "Remote support",
"fieldCompany": "Company", "fieldCompany": "Company",
"fieldEmployeeNumber": "Employee Number",
"fieldResponsibilities": "Responsibilities", "fieldResponsibilities": "Responsibilities",
"fieldWorkplace": "Workplace", "fieldWorkplace": "Workplace",
"fieldJob": "Job", "fieldJob": "Job",
@ -388,7 +391,9 @@
"label": "Update" "label": "Update"
}, },
"notification": { "notification": {
"titleChatEventArrivedByUser": "A Message of chat from {{userInfo}}.",
"titleChatEventArrived": "A message of chat has arrived.", "titleChatEventArrived": "A message of chat has arrived.",
"titleMessageArrivedByUser": "A Message from {{userInfo}}.",
"titleMessageArrived": "A message has arrived." "titleMessageArrived": "A message has arrived."
}, },
"common": { "common": {

View File

@ -45,7 +45,9 @@
"failToChangePassword": "비밀번호 변경에 실패하였습니다.", "failToChangePassword": "비밀번호 변경에 실패하였습니다.",
"loginFailed": "로그인에 실패하였습니다.", "loginFailed": "로그인에 실패하였습니다.",
"loginFailedIdPw": "아이디 또는 패스워드가<br/>일치하지 않습니다.", "loginFailedIdPw": "아이디 또는 패스워드가<br/>일치하지 않습니다.",
"loginFailOverTry": "비밀번호 오류 횟수 초과입니다.<br/>비밀번호를 확인하신 후<br/>잠시 후 다시 시작해 주세요." "loginFailOverTry": "비밀번호 오류 횟수 초과입니다.<br/>비밀번호를 확인하신 후<br/>잠시 후 다시 시작해 주세요.",
"networkFailedAndExit": "네트워크 문제로 프로그램을 종료합니다.<br/>네트워크 확인후 다시 시도해 주세요.",
"networkFailedAndRetry": "네트워크 문제로 실행할 수 없습니다.<br/>네트워크 확인후 다시 시도해 주세요."
} }
}, },
"profile": { "profile": {
@ -57,6 +59,7 @@
"removeBuddy": "동료삭제", "removeBuddy": "동료삭제",
"remoteSupport": "원격 지원", "remoteSupport": "원격 지원",
"fieldCompany": "회사", "fieldCompany": "회사",
"fieldEmployeeNumber": "사번",
"fieldResponsibilities": "담당업무", "fieldResponsibilities": "담당업무",
"fieldWorkplace": "근무지", "fieldWorkplace": "근무지",
"fieldJob": "직무", "fieldJob": "직무",
@ -388,7 +391,9 @@
"label": "업데이트" "label": "업데이트"
}, },
"notification": { "notification": {
"titleChatEventArrivedByUser": "{{userInfo}} 님의 메세지.",
"titleChatEventArrived": "메세지가 도착했습니다.", "titleChatEventArrived": "메세지가 도착했습니다.",
"titleMessageArrivedByUser": "{{userInfo}} 님의 쪽지.",
"titleMessageArrived": "쪽지가 도착했습니다." "titleMessageArrived": "쪽지가 도착했습니다."
}, },
"common": { "common": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -50,7 +50,7 @@ export class DaesangCipherService {
* const dec = this.daesangCipherService.decryptForSapErp('aes256-daesang-key!!',enc); * const dec = this.daesangCipherService.decryptForSapErp('aes256-daesang-key!!',enc);
* console.log('dec', dec); * console.log('dec', dec);
*/ */
encryptForSapErp(pvUserKey: string, employeeNum: number): string { encryptForSapErp(pvUserKey: string, employeeNum: string): string {
// const txt = '20200221090321_asdfghjk'; // 1QgLAiLqJ6Uo6bE4Qk1o3Yd6mfqxXSnmqXX%2FXLL7DoA%3D // const txt = '20200221090321_asdfghjk'; // 1QgLAiLqJ6Uo6bE4Qk1o3Yd6mfqxXSnmqXX%2FXLL7DoA%3D
// const txt = '20200221101444_asdfghjk'; // Lz1TIdGTQQMui%2BBHMdj8fatYYhXbwJEL%2BJ91C7jUWEs%3D // const txt = '20200221101444_asdfghjk'; // Lz1TIdGTQQMui%2BBHMdj8fatYYhXbwJEL%2BJ91C7jUWEs%3D
const str = moment().format('YYYYMMDDHHmmss') + '_' + employeeNum; const str = moment().format('YYYYMMDDHHmmss') + '_' + employeeNum;

View File

@ -28,5 +28,7 @@ export enum WebLinkType {
/** 결재링크 URL */ /** 결재링크 URL */
Payment = 'WebLinkPayment', Payment = 'WebLinkPayment',
/** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */ /** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */
ChgPassword = 'WebLinkChgPassword' ChgPassword = 'WebLinkChgPassword',
/** Erp */
Erp = 'WebLinkERP'
} }

View File

@ -39,12 +39,12 @@ export class SmsUtils {
openSendSms(token: string, employeeNum?: string) { openSendSms(token: string, employeeNum?: string) {
const url = this.url.replace(/(\(%USER_TOKEN%\))/g, token); const url = this.url.replace(/(\(%USER_TOKEN%\))/g, token);
// this.nativeService.openDefaultBrowser(url); this.nativeService.openDefaultBrowser(url + `&ruser=${employeeNum},`);
WindowUtil.popupOpen( // WindowUtil.popupOpen(
url + `&ruser=${employeeNum},`, // url + `&ruser=${employeeNum},`,
'DaesangSMS', // 'DaesangSMS',
685, // 685,
640 // 640
); // );
} }
} }

View File

@ -18,6 +18,7 @@ import { TranslateLoaderService } from '../translate/browser-loader';
import { NotificationService } from '../notification/notification.service'; import { NotificationService } from '../notification/notification.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FileUtil, StatusCode } from '@ucap-webmessenger/core'; import { FileUtil, StatusCode } from '@ucap-webmessenger/core';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -229,11 +230,22 @@ export class BrowserNativeService extends NativeService {
windowMinimize(): void {} windowMinimize(): void {}
windowMaximize(): void {} windowMaximize(): void {}
getWindowState(): WindowState { getWindowState(): {
return WindowState.Normal; windowState: WindowState;
windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
} {
return {
windowState: WindowState.Normal,
windowFocusState: ElectronBrowserWindowChannel.Focus
};
} }
appExit(): void {} appExit(): void {}
appLogging(error: any): void {
console.error('[G]', error);
}
zoomTo(factor: number): Promise<number> { zoomTo(factor: number): Promise<number> {
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {

View File

@ -31,6 +31,7 @@ import { Injectable } from '@angular/core';
import { TranslateLoaderService } from '../translate/electron-loader'; import { TranslateLoaderService } from '../translate/electron-loader';
import { TranslateLoader } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core';
import { StatusCode } from '@ucap-webmessenger/core'; import { StatusCode } from '@ucap-webmessenger/core';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -65,6 +66,10 @@ export class ElectronNativeService implements NativeService {
private backgroundCheckForUpdatesSubject: Subject<UpdateInfo> | null = null; private backgroundCheckForUpdatesSubject: Subject<UpdateInfo> | null = null;
private backgroundCheckForUpdates$: Observable<UpdateInfo> | null = null; private backgroundCheckForUpdates$: Observable<UpdateInfo> | null = null;
private windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
type(): NativeType { type(): NativeType {
return NativeType.Electron; return NativeType.Electron;
} }
@ -395,25 +400,37 @@ export class ElectronNativeService implements NativeService {
} }
} }
getWindowState(): WindowState { getWindowState(): {
windowState: WindowState;
windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
} {
let windowState = WindowState.Normal;
if (!remote.getCurrentWindow().isVisible()) { if (!remote.getCurrentWindow().isVisible()) {
return WindowState.Hidden; windowState = WindowState.Hidden;
} else if (remote.getCurrentWindow().isMinimized()) { } else if (remote.getCurrentWindow().isMinimized()) {
return WindowState.Minimized; windowState = WindowState.Minimized;
} else if (remote.getCurrentWindow().isNormal()) { } else if (remote.getCurrentWindow().isNormal()) {
return WindowState.Normal; windowState = WindowState.Normal;
} else if (remote.getCurrentWindow().isMaximized()) { } else if (remote.getCurrentWindow().isMaximized()) {
return WindowState.Maximized; windowState = WindowState.Maximized;
} else if (remote.getCurrentWindow().isFullScreen()) { } else if (remote.getCurrentWindow().isFullScreen()) {
return WindowState.FullScreen; windowState = WindowState.FullScreen;
} else {
return WindowState.Normal;
} }
return {
windowState,
windowFocusState: this.windowFocusState
};
} }
appExit(): void { appExit(): void {
this.ipcRenderer.send(AppChannel.Exit); this.ipcRenderer.send(AppChannel.Exit);
} }
appLogging(error: any): void {
this.ipcRenderer.send(AppChannel.Logging, error);
}
zoomTo(factor: number): Promise<number> { zoomTo(factor: number): Promise<number> {
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {
@ -529,5 +546,17 @@ export class ElectronNativeService implements NativeService {
this.shell = (window as any).require('electron').shell; this.shell = (window as any).require('electron').shell;
this.webFrame = (window as any).require('electron').webFrame; this.webFrame = (window as any).require('electron').webFrame;
} }
this.ipcRenderer.on(
WindowStateChannel.FocuseChanged,
(
event: any,
status:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur
) => {
this.windowFocusState = status;
}
);
} }
} }

View File

@ -46,7 +46,8 @@ export enum ProcessChannel {
} }
export enum WindowStateChannel { export enum WindowStateChannel {
Changed = 'UCAP::windowState::windowStateChanged' Changed = 'UCAP::windowState::windowStateChanged',
FocuseChanged = 'UCAP::windowState::windowFocusStateChanged'
} }
export enum IdleStateChannel { export enum IdleStateChannel {
@ -61,7 +62,8 @@ export enum ClipboardChannel {
} }
export enum AppChannel { export enum AppChannel {
Exit = 'UCAP::app::exit' Exit = 'UCAP::app::exit',
Logging = 'UCAP::app::logging'
} }
export enum ExternalChannel { export enum ExternalChannel {

View File

@ -7,6 +7,7 @@ import { TranslateLoader } from '@ngx-translate/core';
import { StatusCode } from '@ucap-webmessenger/core'; import { StatusCode } from '@ucap-webmessenger/core';
import { UpdateInfo, UpdateCheckConfig } from '../models/update-info'; import { UpdateInfo, UpdateCheckConfig } from '../models/update-info';
import { NativeType } from '../types/native.type'; import { NativeType } from '../types/native.type';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
export type NativePathName = export type NativePathName =
| 'home' | 'home'
@ -73,9 +74,15 @@ export abstract class NativeService {
abstract windowClose(): void; abstract windowClose(): void;
abstract windowMinimize(): void; abstract windowMinimize(): void;
abstract windowMaximize(): void; abstract windowMaximize(): void;
abstract getWindowState(): WindowState; abstract getWindowState(): {
windowState: WindowState;
windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
};
abstract zoomTo(factor: number): Promise<number>; abstract zoomTo(factor: number): Promise<number>;
abstract appExit(): void; abstract appExit(): void;
abstract appLogging(error: any): void;
abstract idleStateChanged(): Observable<WindowIdle>; abstract idleStateChanged(): Observable<WindowIdle>;
abstract idleStateStop(): void; abstract idleStateStop(): void;

View File

@ -1,6 +1,12 @@
import { ParameterUtil, APIEncoder, APIDecoder } from '@ucap-webmessenger/api'; import {
ParameterUtil,
APIEncoder,
APIDecoder,
HttpUrlEncodingCodec
} from '@ucap-webmessenger/api';
import { PIRequest, PIResponse } from './pi'; import { PIRequest, PIResponse } from './pi';
import { HttpParams, HttpParameterCodec } from '@angular/common/http';
export interface Login2Request extends PIRequest { export interface Login2Request extends PIRequest {
companyCode: string; companyCode: string;
@ -26,7 +32,14 @@ const login2EncodeMap = {
}; };
export const encodeLogin2: APIEncoder<Login2Request> = (req: Login2Request) => { export const encodeLogin2: APIEncoder<Login2Request> = (req: Login2Request) => {
return ParameterUtil.encode(login2EncodeMap, req); let parameter: HttpParams = new HttpParams({
encoder: new HttpUrlEncodingCodec()
});
parameter = parameter.append('companyCd', req.companyCode);
parameter = parameter.append('loginId', req.loginId);
parameter = parameter.append('loginPw', req.loginPw);
return parameter;
// return ParameterUtil.encodeForm(login2EncodeMap, req);
}; };
export const decodeLogin2: APIDecoder<Login2Response> = (res: any) => { export const decodeLogin2: APIDecoder<Login2Response> = (res: any) => {

View File

@ -22,7 +22,7 @@ export interface UserSeqRequest extends ProtocolRequest {
// 기관코드(s) // 기관코드(s)
companyCode: string; companyCode: string;
// 상세정보여부(s) // 상세정보여부(s)
isDetail: boolean; detailType: string;
// 발신자회사코드(s) // 발신자회사코드(s)
senderCompanyCode: string; senderCompanyCode: string;
// 발신자임직원유형(s) // 발신자임직원유형(s)
@ -51,7 +51,7 @@ export const encodeUserSeq: ProtocolEncoder<UserSeqRequest> = (
{ type: PacketBodyValue.String, value: req.divCd }, { type: PacketBodyValue.String, value: req.divCd },
{ type: PacketBodyValue.String, value: req.loginIds.join(',') }, { type: PacketBodyValue.String, value: req.loginIds.join(',') },
{ type: PacketBodyValue.String, value: req.companyCode }, { type: PacketBodyValue.String, value: req.companyCode },
{ type: PacketBodyValue.String, value: req.isDetail ? 'Y' : 'N' }, { type: PacketBodyValue.String, value: req.detailType },
{ type: PacketBodyValue.String, value: req.senderCompanyCode }, { type: PacketBodyValue.String, value: req.senderCompanyCode },
{ type: PacketBodyValue.String, value: req.senderEmployeeType } { type: PacketBodyValue.String, value: req.senderEmployeeType }
); );

View File

@ -39,6 +39,8 @@ export class LoginComponent implements OnInit {
@Input() @Input()
loginId: string; loginId: string;
@Input() @Input()
loginPw?: string;
@Input()
rememberMe: boolean; rememberMe: boolean;
@Input() @Input()
autoLogin: boolean; autoLogin: boolean;
@ -85,6 +87,9 @@ export class LoginComponent implements OnInit {
} }
const loginPwValidators: ValidatorFn[] = [Validators.required]; const loginPwValidators: ValidatorFn[] = [Validators.required];
this.loginPwFormControl.setValidators(loginPwValidators); this.loginPwFormControl.setValidators(loginPwValidators);
if (!!this.loginPw) {
this.loginPwFormControl.setValue(this.loginPw);
}
this.loginForm = this.formBuilder.group({ this.loginForm = this.formBuilder.group({
companyCodeFormControl: this.companyCodeFormControl, companyCodeFormControl: this.companyCodeFormControl,

View File

@ -98,9 +98,7 @@
<div class="message-main-container"> <div class="message-main-container">
<div class="message-main"> <div class="message-main">
<div class="chat-name"> <div class="chat-name">{{ senderName }} {{ senderGrade }}</div>
{{ senderName }}
</div>
<div class="bubble"> <div class="bubble">
<ng-container <ng-container

View File

@ -51,6 +51,9 @@ export class MessageBoxComponent implements OnInit, AfterViewInit {
@Input() @Input()
senderName: string; senderName: string;
@Input()
senderGrade: string;
@Input() @Input()
profileImageRoot: string; profileImageRoot: string;

View File

@ -11,9 +11,12 @@
<span class="bg-accent-dark">{{ 'chat.sentDate' | translate }}</span> <span class="bg-accent-dark">{{ 'chat.sentDate' | translate }}</span>
{{ message.sentMessageJson.postDate | ucapDate: 'YYYY.MM.DD a hh:mm' }} {{ message.sentMessageJson.postDate | ucapDate: 'YYYY.MM.DD a hh:mm' }}
</li> </li>
<li class="event-content"> <li
{{ message.sentMessageJson.content }} class="event-content"
</li> [innerHTML]="
message.sentMessageJson.content | ucapSafeHtml | linefeedtohtml | linky
"
></li>
</ul> </ul>
<!-- <div class="btn-box"> <!-- <div class="btn-box">
<button mat-button (click)="onClickSave()">상세보기</button> <button mat-button (click)="onClickSave()">상세보기</button>

View File

@ -1,16 +1,45 @@
import { Component, OnInit, Input } from '@angular/core'; import {
Component,
OnInit,
Input,
AfterViewInit,
ElementRef,
Inject
} from '@angular/core';
import { Info, AllimEventJson } from '@ucap-webmessenger/protocol-event'; import { Info, AllimEventJson } from '@ucap-webmessenger/protocol-event';
import { UCAP_NATIVE_SERVICE, NativeService } from '@ucap-webmessenger/native';
@Component({ @Component({
selector: 'ucap-chat-message-box-allim', selector: 'ucap-chat-message-box-allim',
templateUrl: './allim.component.html', templateUrl: './allim.component.html',
styleUrls: ['./allim.component.scss'] styleUrls: ['./allim.component.scss']
}) })
export class AllimComponent implements OnInit { export class AllimComponent implements OnInit, AfterViewInit {
@Input() @Input()
message: Info<AllimEventJson>; message: Info<AllimEventJson>;
constructor() {} constructor(
private elementRef: ElementRef,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService
) {}
ngOnInit() {} ngOnInit() {}
ngAfterViewInit(): void {
if (
!!this.elementRef.nativeElement &&
!!this.elementRef.nativeElement.querySelector('a')
) {
const elements = this.elementRef.nativeElement.querySelectorAll('a');
elements.forEach(element => {
element.addEventListener('click', this.onClickEvent.bind(this));
});
}
}
onClickEvent(event: MouseEvent) {
this.nativeService.openDefaultBrowser(
(event.target as HTMLAnchorElement).text
);
}
} }

View File

@ -66,6 +66,7 @@
" "
[dateChanged]="getDateSplitter(message)" [dateChanged]="getDateSplitter(message)"
[senderName]="getUserName(message.senderSeq)" [senderName]="getUserName(message.senderSeq)"
[senderGrade]="getUserGrade(message.senderSeq)"
[profileImageRoot]="profileImageRoot" [profileImageRoot]="profileImageRoot"
[profileImage]="getUserProfile(message.senderSeq)" [profileImage]="getUserProfile(message.senderSeq)"
[roomInfo]="roomInfo" [roomInfo]="roomInfo"

View File

@ -322,6 +322,19 @@ export class MessagesComponent implements OnInit, OnDestroy {
} }
return '(알수없는 사용자)'; return '(알수없는 사용자)';
} }
getUserGrade(seq: number): string {
if (!this.userInfos) {
return '';
}
const userInfo: UserInfo[] = this.userInfos.filter(
user => user.seq === seq
);
if (!!userInfo && userInfo.length > 0) {
return userInfo[0].grade;
}
return '';
}
getUserProfile(seq: number): string { getUserProfile(seq: number): string {
if (!this.userInfos) { if (!this.userInfos) {
return ''; return '';

View File

@ -1,7 +1,11 @@
<mat-card class="example-card profile mat-elevation-z"> <mat-card class="example-card profile mat-elevation-z">
<mat-card-header> <mat-card-header>
<div class="profile-img"> <div class="profile-img">
<div class="profile-img-mask"> <div
class="profile-img-mask"
(click)="onClickProfileImageView()"
style="cursor: pointer;"
>
<img <img
ucapImage ucapImage
[base]="profileImageRoot" [base]="profileImageRoot"
@ -24,7 +28,26 @@
> >
<i class="mid mdi-camera"></i> <i class="mid mdi-camera"></i>
</button> </button>
<!-- <button
mat-mini-fab
class="mat-elevation-z6 icon-button btn-elephant"
*ngIf="!!enableElephantButton"
matTooltip="칭찬 코끼리 보내기"
(click)="onClickSendElephant()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 30 30"
stroke-width="1.5"
stroke="#357abc"
fill="#98d7e2"
>
<path
d="M29,17.3a21.32,21.32,0,0,0-.63-4.39A10.59,10.59,0,0,0,28,11.85c0-.14-.1-.27-.15-.41l-.16-.39a13,13,0,0,0-.81-1.52,11.29,11.29,0,0,0-1.59-2c-.2-.2-.4-.4-.62-.58s-.43-.36-.66-.53a10.83,10.83,0,0,0-1.82-1.09l-.39-.18c-.26-.11-.53-.21-.81-.31A11.06,11.06,0,0,0,6.62,13.71l0,.09v0s0,.07,0,.1v0a.52.52,0,0,0,0,.11h0a.43.43,0,0,1,0,.11h0a.32.32,0,0,1,0,.09v0s0,.05,0,.08h0a.1.1,0,0,1,0,0h0l-.05,0h0l0,0a1.42,1.42,0,0,1-.56-.62,1.84,1.84,0,0,1-.13-1.17A4,4,0,0,1,6,11.55L2.14,9.83h0a6.75,6.75,0,0,0-.84,1.78c-.05.14-.09.29-.13.45A6.18,6.18,0,0,0,1,13.31a5,5,0,0,0,.78,2.84c.08.13.17.26.26.38l.06.08c.09.11.17.22.27.33l0,0,.26.28.07.06.28.25,0,0a2.48,2.48,0,0,0,.26.19l.06,0,.3.19.05,0,.27.14.06,0,.29.13.06,0,.25.09,0,0,.29.09h.06l.24.06h0l.26.05h0l.23,0h0l.23,0h.94l.13.43c.07.19.15.38.23.57s.17.38.27.56a11.26,11.26,0,0,0,1,1.51c.13.16.25.32.39.48a5.34,5.34,0,0,0,.39.44A11.78,11.78,0,0,0,10.9,24.1c.27.2.54.39.83.57a11.72,11.72,0,0,0,1.78,1l.16.06h.19l.13,0h0l.1-.06h0a.39.39,0,0,0,.08-.1h0l.05-.11v0l0-.1h0s0-.06,0-.09v-.08h0v-1a3.27,3.27,0,0,1,.41-1.59,3.36,3.36,0,0,1,2.94-1.76h0a3.31,3.31,0,0,1,1,.16l.3.11a3.15,3.15,0,0,1,.57.31,2.48,2.48,0,0,1,.26.19A3.34,3.34,0,0,1,21,24.08v.79h0v.29l0,0v0a.43.43,0,0,1,0,0v0a.25.25,0,0,0,0,.07h0a.21.21,0,0,0,0,.06v0l0,0,0,0,0,0,0,0,0,0h.18l.09,0a11,11,0,0,0,1.65-.82c.27-.16.53-.34.78-.52a10.65,10.65,0,0,0,1.07-.93c.11-.12.22-.23.32-.35a7.25,7.25,0,0,0,.58-.76l.24-.4a6.43,6.43,0,0,0,.41-.88l.15-.46.15.64c0,.09,0,.18.05.27v.09l0,.18v.1l0,.19V22l0,.2v.05l0,.2v.07c0,.05,0,.09,0,.14v.07a.69.69,0,0,1,0,.13V23a.94.94,0,0,1,0,.16v0a.56.56,0,0,0,0,.12v0l0,.08v0l0,.06,0,0a.1.1,0,0,0,.05,0h.07l.05,0s0,0,0,0a.12.12,0,0,0,.05,0l0,0a.52.52,0,0,0,.08-.11A9.41,9.41,0,0,0,29,18.52C29,18.12,29,17.71,29,17.3ZM10.55,11.59a.86.86,0,1,1,0-1.71.85.85,0,0,1,.85.85A.86.86,0,0,1,10.55,11.59Zm9.79,4.79A5.27,5.27,0,0,1,17,18.07a2.67,2.67,0,0,1-2-.95,7.21,7.21,0,0,1-1.69-4.84c.07-1.18.76-4,5.58-4.94a4.18,4.18,0,0,1,.61,0v0a3.43,3.43,0,0,1,3.11,2.29C23.5,11.73,22.63,14.27,20.34,16.38Z"
transform="translate(-1 -4.29)"
/>
</svg>
</button> -->
<span <span
*ngIf="getWorkstatus(userInfo).length > 0" *ngIf="getWorkstatus(userInfo).length > 0"
class="work-status" class="work-status"
@ -180,6 +203,12 @@
<dt class="division">{{ 'profile.fieldCompany' | translate }}</dt> <dt class="division">{{ 'profile.fieldCompany' | translate }}</dt>
<dd>{{ userInfo.companyName | ucapStringEmptycheck }}</dd> <dd>{{ userInfo.companyName | ucapStringEmptycheck }}</dd>
</li> </li>
<li class="employeeNum">
<dt class="division">
{{ 'profile.fieldEmployeeNumber' | translate }}
</dt>
<dd>{{ userInfo.employeeNum | slice: 2 }}</dd>
</li>
<li class="deptName"> <li class="deptName">
<dt class="division">{{ 'search.fieldDeptartment' | translate }}</dt> <dt class="division">{{ 'search.fieldDeptartment' | translate }}</dt>
<dd>{{ userInfo | ucapTranslate: 'deptName' }}</dd> <dd>{{ userInfo | ucapTranslate: 'deptName' }}</dd>
@ -379,6 +408,31 @@
}}</span> }}</span>
</div> </div>
<!-- <div class="button-text-item" *ngIf="!!enableElephantButton">
<button
mat-mini-fab
class="mat-elevation-z bg-accent-darkest"
*ngIf="!isMe"
matTooltip="칭찬 코끼리"
matTooltipPosition="above"
(click)="onClickSendElephant()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 30 30"
stroke-width="1.5"
stroke="#357abc"
fill="#98d7e2"
>
<path
d="M29,17.3a21.32,21.32,0,0,0-.63-4.39A10.59,10.59,0,0,0,28,11.85c0-.14-.1-.27-.15-.41l-.16-.39a13,13,0,0,0-.81-1.52,11.29,11.29,0,0,0-1.59-2c-.2-.2-.4-.4-.62-.58s-.43-.36-.66-.53a10.83,10.83,0,0,0-1.82-1.09l-.39-.18c-.26-.11-.53-.21-.81-.31A11.06,11.06,0,0,0,6.62,13.71l0,.09v0s0,.07,0,.1v0a.52.52,0,0,0,0,.11h0a.43.43,0,0,1,0,.11h0a.32.32,0,0,1,0,.09v0s0,.05,0,.08h0a.1.1,0,0,1,0,0h0l-.05,0h0l0,0a1.42,1.42,0,0,1-.56-.62,1.84,1.84,0,0,1-.13-1.17A4,4,0,0,1,6,11.55L2.14,9.83h0a6.75,6.75,0,0,0-.84,1.78c-.05.14-.09.29-.13.45A6.18,6.18,0,0,0,1,13.31a5,5,0,0,0,.78,2.84c.08.13.17.26.26.38l.06.08c.09.11.17.22.27.33l0,0,.26.28.07.06.28.25,0,0a2.48,2.48,0,0,0,.26.19l.06,0,.3.19.05,0,.27.14.06,0,.29.13.06,0,.25.09,0,0,.29.09h.06l.24.06h0l.26.05h0l.23,0h0l.23,0h.94l.13.43c.07.19.15.38.23.57s.17.38.27.56a11.26,11.26,0,0,0,1,1.51c.13.16.25.32.39.48a5.34,5.34,0,0,0,.39.44A11.78,11.78,0,0,0,10.9,24.1c.27.2.54.39.83.57a11.72,11.72,0,0,0,1.78,1l.16.06h.19l.13,0h0l.1-.06h0a.39.39,0,0,0,.08-.1h0l.05-.11v0l0-.1h0s0-.06,0-.09v-.08h0v-1a3.27,3.27,0,0,1,.41-1.59,3.36,3.36,0,0,1,2.94-1.76h0a3.31,3.31,0,0,1,1,.16l.3.11a3.15,3.15,0,0,1,.57.31,2.48,2.48,0,0,1,.26.19A3.34,3.34,0,0,1,21,24.08v.79h0v.29l0,0v0a.43.43,0,0,1,0,0v0a.25.25,0,0,0,0,.07h0a.21.21,0,0,0,0,.06v0l0,0,0,0,0,0,0,0,0,0h.18l.09,0a11,11,0,0,0,1.65-.82c.27-.16.53-.34.78-.52a10.65,10.65,0,0,0,1.07-.93c.11-.12.22-.23.32-.35a7.25,7.25,0,0,0,.58-.76l.24-.4a6.43,6.43,0,0,0,.41-.88l.15-.46.15.64c0,.09,0,.18.05.27v.09l0,.18v.1l0,.19V22l0,.2v.05l0,.2v.07c0,.05,0,.09,0,.14v.07a.69.69,0,0,1,0,.13V23a.94.94,0,0,1,0,.16v0a.56.56,0,0,0,0,.12v0l0,.08v0l0,.06,0,0a.1.1,0,0,0,.05,0h.07l.05,0s0,0,0,0a.12.12,0,0,0,.05,0l0,0a.52.52,0,0,0,.08-.11A9.41,9.41,0,0,0,29,18.52C29,18.12,29,17.71,29,17.3ZM10.55,11.59a.86.86,0,1,1,0-1.71.85.85,0,0,1,.85.85A.86.86,0,0,1,10.55,11.59Zm9.79,4.79A5.27,5.27,0,0,1,17,18.07a2.67,2.67,0,0,1-2-.95,7.21,7.21,0,0,1-1.69-4.84c.07-1.18.76-4,5.58-4.94a4.18,4.18,0,0,1,.61,0v0a3.43,3.43,0,0,1,3.11,2.29C23.5,11.73,22.63,14.27,20.34,16.38Z"
transform="translate(-1 -4.29)"
/>
</svg>
</button>
<span class="button-text">칭찬 코끼리</span>
</div> -->
<div class="button-text-item" *ngIf="!isMe"> <div class="button-text-item" *ngIf="!isMe">
<button <button
mat-mini-fab mat-mini-fab

View File

@ -74,6 +74,7 @@ $login-max-height: 800px;
.work-status { .work-status {
display: inline-flex; display: inline-flex;
margin-left: 20px;
height: 24px; height: 24px;
border: 1px solid #ffffff; border: 1px solid #ffffff;
padding: 4px 14px; padding: 4px 14px;
@ -227,6 +228,26 @@ $login-max-height: 800px;
} }
} }
.btn-elephant {
position: absolute;
background-color: #ffffff !important;
justify-content: center;
align-items: center;
left: 60px;
top: 46px;
svg {
width: 30px;
height: 30px;
}
&:hover {
background-color: #98d7e2 !important;
opacity: 1;
svg {
fill: #ffffff;
}
}
}
.userInfo-call { .userInfo-call {
position: relative; position: relative;
display: flex; display: flex;

View File

@ -44,7 +44,11 @@ export class ProfileComponent implements OnInit {
useBuddyToggleButton: boolean; useBuddyToggleButton: boolean;
@Input() @Input()
authInfo: AuthResponse; authInfo: AuthResponse;
@Input()
enableElephantButton: boolean; // 칭찬코끼리 버튼 활성화 대상 여부.
@Output()
profileImageView = new EventEmitter<void>();
@Output() @Output()
openChat = new EventEmitter<UserInfoSS>(); openChat = new EventEmitter<UserInfoSS>();
@Output() @Output()
@ -70,6 +74,8 @@ export class ProfileComponent implements OnInit {
@Output() @Output()
updateIntro = new EventEmitter<string>(); updateIntro = new EventEmitter<string>();
@Output() @Output()
sendElephant = new EventEmitter();
@Output()
close = new EventEmitter(); close = new EventEmitter();
@ViewChild('profileImageFileInput', { static: false }) @ViewChild('profileImageFileInput', { static: false })
@ -86,6 +92,14 @@ export class ProfileComponent implements OnInit {
ngOnInit() {} ngOnInit() {}
onClickProfileImageView() {
this.profileImageView.emit();
}
onClickSendElephant() {
this.sendElephant.emit();
}
onClickOpenChat() { onClickOpenChat() {
this.openChat.emit(this.userInfo); this.openChat.emit(this.userInfo);
} }

View File

@ -13,7 +13,10 @@
*ngSwitchCase="FileViewerType.Image" *ngSwitchCase="FileViewerType.Image"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
[imageOnly]="imageOnly"
[imageOnlyData]="imageOnlyData"
(download)="onDownload($event)" (download)="onDownload($event)"
(saveAs)="onSaveAs($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-image-viewer> ></ucap-image-viewer>
<ucap-sound-viewer <ucap-sound-viewer
@ -28,6 +31,7 @@
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload($event)" (download)="onDownload($event)"
(saveAs)="onSaveAs($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-video-viewer> ></ucap-video-viewer>
<ucap-binary-viewer <ucap-binary-viewer
@ -35,6 +39,7 @@
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[fileDownloadUrl]="fileDownloadUrl" [fileDownloadUrl]="fileDownloadUrl"
(download)="onDownload($event)" (download)="onDownload($event)"
(saveAs)="onSaveAs($event)"
(closed)="onClosedViewer()" (closed)="onClosedViewer()"
></ucap-binary-viewer> ></ucap-binary-viewer>
</div> </div>

View File

@ -4,6 +4,7 @@ import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { FileViewerType } from '../types/file-viewer.type'; import { FileViewerType } from '../types/file-viewer.type';
import { FileType } from '@ucap-webmessenger/protocol-file'; import { FileType } from '@ucap-webmessenger/protocol-file';
import { FileDownloadItem } from '@ucap-webmessenger/api'; import { FileDownloadItem } from '@ucap-webmessenger/api';
import { ImageOnlyDataInfo } from '../models/image-only-data-info';
@Component({ @Component({
selector: 'ucap-file-viewer', selector: 'ucap-file-viewer',
@ -18,8 +19,15 @@ export class FileViewerComponent implements OnInit {
@Input() @Input()
fileDownloadUrl: string; fileDownloadUrl: string;
@Input()
imageOnly = false;
@Input()
imageOnlyData?: ImageOnlyDataInfo;
@Output() @Output()
download = new EventEmitter<FileDownloadItem>(); download = new EventEmitter<FileDownloadItem>();
@Output()
saveAs = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -31,6 +39,10 @@ export class FileViewerComponent implements OnInit {
ngOnInit() {} ngOnInit() {}
detectFileViewerType(fileInfo: FileEventJson): FileViewerType { detectFileViewerType(fileInfo: FileEventJson): FileViewerType {
if (!!this.imageOnly) {
return FileViewerType.Image;
}
switch (fileInfo.fileType) { switch (fileInfo.fileType) {
case FileType.Image: case FileType.Image:
return FileViewerType.Image; return FileViewerType.Image;
@ -48,6 +60,9 @@ export class FileViewerComponent implements OnInit {
onDownload(fileDownloadItem: FileDownloadItem): void { onDownload(fileDownloadItem: FileDownloadItem): void {
this.download.emit(fileDownloadItem); this.download.emit(fileDownloadItem);
} }
onSaveAs(fileDownloadItem: FileDownloadItem): void {
this.saveAs.emit(fileDownloadItem);
}
onClosedViewer(): void { onClosedViewer(): void {
this.closed.emit(); this.closed.emit();

View File

@ -44,6 +44,31 @@
/> />
</svg> </svg>
</button> </button>
<button
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.saveAs' | translate }}"
matTooltipPosition="below"
aria-label=""
(click)="onClickSaveAs()"
>
<!--<mat-icon>get_app</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg>
</button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
<button <button
mat-icon-button mat-icon-button
@ -94,7 +119,7 @@
<div class="guide-msg"> <div class="guide-msg">
{{ 'common.file.errors.noPreview' | translate }} {{ 'common.file.errors.noPreview' | translate }}
</div> </div>
<div> <div class="btn-group">
<button <button
colori colori
mat-raised-button mat-raised-button
@ -103,6 +128,14 @@
> >
{{ 'common.file.download' | translate }} {{ 'common.file.download' | translate }}
</button> </button>
<button
colori
mat-raised-button
aria-label=""
(click)="onClickSaveAs()"
>
{{ 'common.file.saveAs' | translate }}
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -60,5 +60,10 @@
margin: 30px; margin: 30px;
color: #ffffff; color: #ffffff;
} }
.btn-group {
button {
margin: 0 5px;
}
}
} }
} }

View File

@ -18,6 +18,8 @@ export class BinaryViewerComponent implements OnInit {
@Output() @Output()
download = new EventEmitter<FileDownloadItem>(); download = new EventEmitter<FileDownloadItem>();
@Output()
saveAs = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -33,6 +35,11 @@ export class BinaryViewerComponent implements OnInit {
this.download.emit(this.fileDownloadItem); this.download.emit(this.fileDownloadItem);
} }
onClickSaveAs(): void {
this.fileDownloadItem = new FileDownloadItem();
this.saveAs.emit(this.fileDownloadItem);
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();
} }

View File

@ -17,10 +17,21 @@
<circle cx="8.5" cy="8.5" r="1.5" /> <circle cx="8.5" cy="8.5" r="1.5" />
<path d="M20.4 14.5L16 10 4 20" /> <path d="M20.4 14.5L16 10 4 20" />
</svg> </svg>
<span class="ucap-image-viewer-title">{{ fileInfo.fileName }}</span> <span class="ucap-image-viewer-title">
<ng-container
*ngIf="imageOnly; then imageOnlyName; else defaultName"
></ng-container>
<ng-template #imageOnlyName>
{{ imageOnlyData.fileName }}
</ng-template>
<ng-template #defaultName>
{{ fileInfo.fileName }}
</ng-template>
</span>
<span class="ucap-image-viewer-spacer"></span> <span class="ucap-image-viewer-spacer"></span>
<button <button
*ngIf="!imageOnly"
mat-icon-button mat-icon-button
class="ucap-image-viewer-action" class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomReset' | translate }}" matTooltip="{{ 'common.messages.zoomReset' | translate }}"
@ -47,6 +58,7 @@
</svg> </svg>
</button> </button>
<button <button
*ngIf="!imageOnly"
mat-icon-button mat-icon-button
class="ucap-image-viewer-action" class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomOut' | translate }}" matTooltip="{{ 'common.messages.zoomOut' | translate }}"
@ -72,6 +84,7 @@
</svg> </svg>
</button> </button>
<button <button
*ngIf="!imageOnly"
mat-icon-button mat-icon-button
class="ucap-image-viewer-action" class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomIn' | translate }}" matTooltip="{{ 'common.messages.zoomIn' | translate }}"
@ -98,6 +111,7 @@
</svg> </svg>
</button> </button>
<button <button
*ngIf="!imageOnly"
mat-icon-button mat-icon-button
class="ucap-image-viewer-action" class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.download' | translate }}" matTooltip="{{ 'common.file.download' | translate }}"
@ -122,6 +136,32 @@
/> />
</svg> </svg>
</button> </button>
<button
*ngIf="!imageOnly"
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.saveAs' | translate }}"
matTooltipPosition="below"
aria-label=""
(click)="onClickSaveAs()"
>
<!--<mat-icon>get_app</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg>
</button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
<button <button
mat-icon-button mat-icon-button
@ -166,6 +206,20 @@
fxFlexFill fxFlexFill
fxLayoutAlign="center center" fxLayoutAlign="center center"
> >
<ng-container
*ngIf="imageOnly; then imageOnlyImg; else defaultImg"
></ng-container>
<ng-template #imageOnlyImg>
<img
ucapImage
[base]="imageOnlyData.imageRootUrl"
[path]="imageOnlyData.imageUrl"
[default]="imageOnlyData.defaultImage"
style="cursor: pointer; width: auto;"
(click)="onClickClose()"
/>
</ng-template>
<ng-template #defaultImg>
<img <img
#downloadImage #downloadImage
*ngIf="fileDownloadUrl" *ngIf="fileDownloadUrl"
@ -174,6 +228,7 @@
[style.height]="imageHeight + 'px'" [style.height]="imageHeight + 'px'"
(load)="onLoadFileDownloadUrl(downloadImage)" (load)="onLoadFileDownloadUrl(downloadImage)"
/> />
</ng-template>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,6 +13,7 @@ import {
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { FileDownloadItem } from '@ucap-webmessenger/api'; import { FileDownloadItem } from '@ucap-webmessenger/api';
import { ImageOnlyDataInfo } from '../../models/image-only-data-info';
@Component({ @Component({
selector: 'ucap-image-viewer', selector: 'ucap-image-viewer',
@ -28,11 +29,18 @@ export class ImageViewerComponent implements OnInit {
@Input() @Input()
fileDownloadUrl: string; fileDownloadUrl: string;
@Input()
imageOnly = false;
@Input()
imageOnlyData?: ImageOnlyDataInfo;
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@Output() @Output()
download = new EventEmitter<FileDownloadItem>(); download = new EventEmitter<FileDownloadItem>();
@Output()
saveAs = new EventEmitter<FileDownloadItem>();
@ViewChild('imageContainer', { static: false }) @ViewChild('imageContainer', { static: false })
imageContainer: ElementRef<HTMLElement>; imageContainer: ElementRef<HTMLElement>;
@ -67,6 +75,11 @@ export class ImageViewerComponent implements OnInit {
this.download.emit(this.fileDownloadItem); this.download.emit(this.fileDownloadItem);
} }
onClickSaveAs(): void {
this.fileDownloadItem = new FileDownloadItem();
this.saveAs.emit(this.fileDownloadItem);
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();
} }

View File

@ -28,6 +28,31 @@
/> />
</svg> </svg>
</button> </button>
<button
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.saveAs' | translate }}"
matTooltipPosition="below"
aria-label=""
(click)="onClickSaveAs()"
>
<!--<mat-icon>get_app</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg>
</button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
<button <button
mat-icon-button mat-icon-button

View File

@ -27,6 +27,8 @@ export class SoundViewerComponent implements OnInit {
@Output() @Output()
download = new EventEmitter<FileDownloadItem>(); download = new EventEmitter<FileDownloadItem>();
@Output()
saveAs = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -95,6 +97,11 @@ export class SoundViewerComponent implements OnInit {
this.download.emit(this.fileDownloadItem); this.download.emit(this.fileDownloadItem);
} }
onClickSaveAs(): void {
this.fileDownloadItem = new FileDownloadItem();
this.saveAs.emit(this.fileDownloadItem);
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();
} }

View File

@ -49,6 +49,31 @@
/> />
</svg> </svg>
</button> </button>
<button
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.saveAs' | translate }}"
matTooltipPosition="below"
aria-label=""
(click)="onClickSaveAs()"
>
<!--<mat-icon>get_app</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"
/>
</svg>
</button>
<span class="stroke-bar"></span> <span class="stroke-bar"></span>
<button <button
mat-icon-button mat-icon-button

View File

@ -30,6 +30,8 @@ export class VideoViewerComponent implements OnInit {
@Output() @Output()
download = new EventEmitter<FileDownloadItem>(); download = new EventEmitter<FileDownloadItem>();
@Output()
saveAs = new EventEmitter<FileDownloadItem>();
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ -116,6 +118,11 @@ export class VideoViewerComponent implements OnInit {
this.download.emit(this.fileDownloadItem); this.download.emit(this.fileDownloadItem);
} }
onClickSaveAs(): void {
this.fileDownloadItem = new FileDownloadItem();
this.saveAs.emit(this.fileDownloadItem);
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();
} }

View File

@ -125,7 +125,7 @@
</th> </th>
<td mat-cell *matCellDef="let element" class="lineNumber"> <td mat-cell *matCellDef="let element" class="lineNumber">
<div class="lineNumber"> <div class="lineNumber">
{{ element.lineNumber }} {{ element.lineNumber | ucapStringFormatterPhone }}
</div> </div>
</td> </td>
</ng-container> </ng-container>
@ -140,7 +140,7 @@
</th> </th>
<td mat-cell *matCellDef="let element" class="hpNumber"> <td mat-cell *matCellDef="let element" class="hpNumber">
<div class="hpNumber"> <div class="hpNumber">
{{ element.hpNumber }} {{ element.hpNumber | ucapStringFormatterPhone }}
</div> </div>
</td> </td>
</ng-container> </ng-container>

View File

@ -0,0 +1,6 @@
export interface ImageOnlyDataInfo {
imageUrl: string;
imageRootUrl: string;
defaultImage: string;
fileName: string;
}

View File

@ -30,6 +30,8 @@ export * from './lib/directives/click-outside.directive';
export * from './lib/directives/file-upload-for.directive'; export * from './lib/directives/file-upload-for.directive';
export * from './lib/directives/image.directive'; export * from './lib/directives/image.directive';
export * from './lib/models/image-only-data-info';
export * from './lib/services/bottom-sheet.service'; export * from './lib/services/bottom-sheet.service';
export * from './lib/services/clipboard.service'; export * from './lib/services/clipboard.service';
export * from './lib/services/dialog.service'; export * from './lib/services/dialog.service';