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",
"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',
Closed = 'closed',
ReadyToShow = 'ready-to-show',
Focus = 'focus'
Focus = 'focus',
Blur = 'blur'
}
export enum ElectronWebContentsChannel {

View File

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

View File

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

View File

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

View File

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

View File

@ -192,6 +192,14 @@ export const decodeUrlInfoDaesang: APIDecoder<DaesangUrlInfoResponse> = (
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 {
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/httpUrlEncodingCodec';
export * from './lib/types/message-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 { environment } from '../environments/environment';
import { ERRORHANDLER } from './error-handler';
@NgModule({
imports: [
@ -105,7 +106,7 @@ import { environment } from '../environments/environment';
level: NgxLoggerLevel.DEBUG
})
],
providers: [...GUARDS],
providers: [...GUARDS, ERRORHANDLER],
declarations: [AppComponent],
bootstrap: [AppComponent],
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 (
!!appUserInfo &&
appUserInfo.settings.general.autoLogin &&
!!appUserInfo.settings.general.autoLogin &&
!(!!personLogout && !!personLogout.personLogout)
) {
this.store.dispatch(

View File

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

View File

@ -7,6 +7,9 @@ import { DeviceType } from '@ucap-webmessenger/core';
import { FileDownloadItem } from '@ucap-webmessenger/api';
import { CommonApiService } from '@ucap-webmessenger/api-common';
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 {
fileInfo: FileEventJson;
@ -14,6 +17,8 @@ export interface FileViewerDialogData {
userSeq: number;
deviceType: DeviceType;
token: string;
imageOnly?: boolean;
imageOnlyData?: ImageOnlyDataInfo;
}
export interface FileViewerDialogResult {}
@ -32,6 +37,9 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
fileDownloadUrl: string;
imageOnly = false;
imageOnlyData: ImageOnlyDataInfo;
constructor(
public dialogRef: MatDialogRef<
FileViewerDialogData,
@ -40,6 +48,9 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
@Inject(MAT_DIALOG_DATA) public data: FileViewerDialogData,
private commonApiService: CommonApiService,
private appFileService: AppFileService,
private translateService: TranslateService,
private snackBarService: SnackBarService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private logger: NGXLogger
) {
this.fileInfo = data.fileInfo;
@ -47,19 +58,28 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
this.userSeq = data.userSeq;
this.deviceType = data.deviceType;
this.token = data.token;
this.fileDownloadUrl = this.commonApiService.urlForFileTalkDownload(
{
userSeq: this.userSeq,
deviceType: this.deviceType,
token: this.token,
attachmentsSeq: this.fileInfo.attachmentSeq
},
this.downloadUrl
);
}
ngOnInit() {}
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(
{
userSeq: this.userSeq,
deviceType: this.deviceType,
token: this.token,
attachmentsSeq: this.fileInfo.attachmentSeq
},
this.downloadUrl
);
}
}
ngOnDestroy(): void {}
@ -76,6 +96,47 @@ export class FileViewerDialogComponent implements OnInit, OnDestroy {
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 {
this.dialogRef.close();

View File

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

View File

@ -318,6 +318,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(select(AppStore.MessengerSelector.RoomSelector.selectUserinfolist))
.subscribe(userInfoList => {
this.userInfoListSubject.next(userInfoList);
this.changeDetectorRef.detectChanges();
});
this.eventListProcessing$ = this.store.pipe(
@ -1585,7 +1586,32 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
break;
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.seq !== this.loginResSubject.value.userSeq && user.isJoinRoom
);

View File

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

View File

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

View File

@ -255,15 +255,55 @@
[selected]="getChipsRemoveYn(userInfo)"
(removed)="onClickDeleteUser(userInfo)"
>
{{ userInfo.name }}
{{ userInfo | ucapTranslate: 'name' }}
<mat-icon matChipRemove *ngIf="getChipsRemoveYn(userInfo)"
>clear</mat-icon
>
</mat-chip>
</mat-chip-list>
</div>
<span>
{{ selectedUserList.length }}
{{ 'common.units.persons' | translate }}
</span>
<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>
{{ selectedUserList.length }}
{{ 'common.units.persons' | translate }}
</span>
</ng-template>
</ng-template>

View File

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

View File

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

View File

@ -4,9 +4,13 @@ import {
KEY_LOGIN_RES_INFO,
KEY_VER_INFO,
KEY_AUTH_INFO,
MainMenu
MainMenu,
KEY_URL_INFO
} 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 * as AppStore from '@app/store';
@ -30,7 +34,8 @@ import {
SnackBarService,
AlertDialogComponent,
AlertDialogResult,
AlertDialogData
AlertDialogData,
ImageOnlyDataInfo
} from '@ucap-webmessenger/ui';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
@ -57,11 +62,20 @@ import {
ConferenceService
} from '@ucap-webmessenger/api-prompt';
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 { environment } from '../../../../../environments/environment';
import { TranslateService } from '@ngx-translate/core';
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 {
userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN;
@ -99,6 +113,7 @@ export class ProfileDialogComponent implements OnInit, OnDestroy {
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
@Inject(MAT_DIALOG_DATA) public data: ProfileDialogData,
private dialogService: DialogService,
private localStorageService: LocalStorageService,
private sessionStorageService: SessionStorageService,
private commonApiService: CommonApiService,
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) {
this.store.dispatch(
AuthenticationStore.infoUser({

View File

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

View File

@ -1,20 +1,12 @@
import { Component, OnInit, Inject, Renderer2, OnDestroy } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import {
KEY_LOGIN_RES_INFO,
KEY_VER_INFO,
KEY_LOGIN_INFO,
KEY_URL_INFO,
KEY_AUTH_INFO,
KEY_LOGOUT_INFO
} from '@app/types';
import { KEY_LOGIN_RES_INFO, KEY_VER_INFO } from '@app/types';
import {
SessionStorageService,
LocalStorageService
} from '@ucap-webmessenger/web-storage';
import { Store } from '@ngrx/store';
import * as AuthenticationStore from '@app/store/account/authentication';
import clone from 'clone';
@ -210,17 +202,4 @@ export class MessengerSettingsDialogComponent implements OnInit, OnDestroy {
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>
</button>
<button
mat-icon-button
*ngSwitchCase="WebLinkType.Erp"
class="button"
[matTooltip]="link.title"
(click)="onClickWebLink(link)"
>
<span class="weblink erp">ERP</span>
</button>
<button
mat-icon-button
*ngSwitchCase="WebLinkType.Mail"
@ -447,10 +456,13 @@
</mat-menu>
<mat-menu #informationMenu="matMenu">
<ng-template matMenuContent>
<ng-template matMenuContent let-isShowClearBtn="false">
<div class="version-info-container menu-item">
<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
class="info-content"
>{{ appVersion }}</span
@ -491,5 +503,14 @@
</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>
</mat-menu>

View File

@ -95,7 +95,8 @@
&.dsp {
text-indent: 0;
}
&.sms {
&.sms,
&.erp {
text-indent: 0;
letter-spacing: -1px;
}
@ -110,6 +111,9 @@
font-size: 11px;
letter-spacing: -1px;
}
&.elephant {
background-position: 50% 90%;
}
}
&.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");
}
.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 {
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_VER_INFO,
EnvironmentsInfo,
KEY_ENVIRONMENTS_INFO
KEY_ENVIRONMENTS_INFO,
KEY_LOGIN_RES_INFO,
KEY_LOGOUT_INFO,
KEY_AUTH_INFO
} from '@app/types';
import {
WebLink,
@ -62,7 +65,12 @@ import {
ProfileDialogResult,
ProfileDialogData
} 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 { MatMenu, MatMenuTrigger } from '@angular/material/menu';
import { StatusCode, StatusType, WindowUtil } from '@ucap-webmessenger/core';
@ -110,6 +118,8 @@ export class TopBarComponent implements OnInit, OnDestroy {
webLinkBadgeMail = 0;
webLinkBadgePayment = 0;
webLinkbadgeEmailCountInterval: any;
appVersion: string;
WebLinkType = WebLinkType;
@ -215,6 +225,10 @@ export class TopBarComponent implements OnInit, OnDestroy {
this.zoomSubscription.unsubscribe();
this.zoomSubscription = undefined;
}
if (!!this.webLinkbadgeEmailCountInterval) {
clearInterval(this.webLinkbadgeEmailCountInterval);
}
}
initWebLink(loginRes: LoginResponse): void {
@ -246,9 +260,9 @@ export class TopBarComponent implements OnInit, OnDestroy {
);
const WebLinkMailCnt = link[0];
const loginPw = appUserInfo.loginPw;
const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId;
const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = loginRes.tokenString;
const url = WebLinkMailCnt.url
@ -264,6 +278,19 @@ export class TopBarComponent implements OnInit, OnDestroy {
catchError(error => of(this.logger.log(error)))
)
.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) {
@ -278,9 +305,9 @@ export class TopBarComponent implements OnInit, OnDestroy {
);
const WebLinkPaymentCnt = link[0];
const loginPw = appUserInfo.loginPw;
const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId;
const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = loginRes.tokenString;
const url = WebLinkPaymentCnt.url
@ -406,65 +433,74 @@ export class TopBarComponent implements OnInit, OnDestroy {
environment.customConfig.appKey
);
const loginPw = appUserInfo.loginPw;
const loginPw = encodeURIComponent(appUserInfo.loginPw);
const loginPw2 = this.loginInfo.loginPw;
const loginId = this.loginInfo.loginId;
const loginId = encodeURIComponent(this.loginInfo.loginId);
const token = this.loginRes.tokenString;
const erpPw = this.daesangCipherService.encryptForSapErp(
'aes256-daesang-key!!',
this.loginRes.userInfo.employeeNum
);
const url = link.url
.replace(/(\(%USER_TOKEN%\))/g, token)
.replace(/(\(%USER_ID%\))/g, loginId)
.replace(/(\(%USER_PASS%\))/g, loginPw);
.replace(/(\(%USER_PASS%\))/g, loginPw)
.replace(/(\(%ENC_PASSWD%\))/g, erpPw);
let width = 1024;
let height = 768;
let openType = 'INNER-POPUP';
switch (link.key) {
case WebLinkType.Sms:
/** SMS URL */
{
width = 685;
height = 640;
}
break;
// /** SMS URL */
// case WebLinkType.Sms:
// {
// width = 685;
// height = 640;
// }
// break;
// /** IT서비스데스크 URL */
// case WebLinkType.Itsvcdesk:
// /** IT서비스데스크 URL */
// {
// width = 1400;
// height = 1000;
// }
// break;
/** 화상회의 URL */
case WebLinkType.Conf:
/** 화상회의 URL */
{
}
break;
case WebLinkType.Itsvcdesk:
/** SMS URL */
case WebLinkType.Sms:
/** IT서비스데스크 URL */
case WebLinkType.Dsp:
case WebLinkType.Itsvcdesk:
/** DSP URL */
case WebLinkType.Webhard:
case WebLinkType.Dsp:
/** 웹하드 URL */
case WebLinkType.Ep:
case WebLinkType.Webhard:
/** EP URL */
case WebLinkType.Sop:
case WebLinkType.Ep:
/** S&OP회의 URL */
case WebLinkType.Som:
case WebLinkType.Sop:
/** S&OM회의 URL */
case WebLinkType.Elephant:
case WebLinkType.Som:
/** 코끼리 URL */
case WebLinkType.UrgntNews:
case WebLinkType.Elephant:
/** 개인속보 URL */
case WebLinkType.MailCnt:
case WebLinkType.UrgntNews:
/** 메일Count URL */
case WebLinkType.Mail:
case WebLinkType.MailCnt:
/** 메일 링크 URL */
case WebLinkType.PaymentCnt:
case WebLinkType.Mail:
/** 결재Count URL */
case WebLinkType.Payment:
case WebLinkType.PaymentCnt:
/** 결재링크 URL */
case WebLinkType.Payment:
/** Erp URL */
case WebLinkType.Erp:
/** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */
case WebLinkType.ChgPassword:
/** 비밀번호변경 URL ; PC 메신저만 해당 비밀번호 만료시 */
{
openType = 'DEFAULT-BROWSER';
}
@ -692,4 +728,31 @@ export class TopBarComponent implements OnInit, OnDestroy {
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>
-->
<ucap-account-login
[companyList]="companyList$ | async"
[companyList]="companyList"
[curCompanyCode]="fixedCompany"
[notiText]="fixedNotiBtnText"
[loginBtnEnable]="loginBtnEnable"
[loginBtnText]="loginBtnText"
[companyCode]="appUserInfo?.companyCode"
[loginId]="appUserInfo?.loginId"
[loginPw]="loginPw"
[rememberMe]="appUserInfo?.rememberMe"
[autoLogin]="appUserInfo?.settings?.general?.autoLogin"
[autoLogin]="autologin"
[useRememberMe]="useRememberMe"
[useAutoLogin]="useAutoLogin"
(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 { Company } from '@ucap-webmessenger/api-external';
import {
Company,
ExternalApiService,
CompanyListRequest
} from '@ucap-webmessenger/api-external';
import { ServerErrorCode, ProtocolService } from '@ucap-webmessenger/protocol';
import * as AppStore from '@app/store';
import * as AuthenticationStore from '@app/store/account/authentication';
import * as CompanyStore from '@app/store/setting/company';
import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable, Subscription, of } from 'rxjs';
import { map, take, catchError } from 'rxjs/operators';
import {
DialogService,
AlertDialogComponent,
@ -34,6 +38,7 @@ import { AppAuthenticationService } from '@app/services/authentication.service';
import { logoutInitialize } from '@app/store/account/authentication';
import { TranslateService } from '@ngx-translate/core';
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
import { StatusCode } from '@ucap-webmessenger/api';
@Component({
selector: 'app-page-account-login',
@ -44,7 +49,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {
fixedCompany: string;
fixedNotiBtnText: string;
companyList$: Observable<Company[]>;
companyList: Company[];
companyListSubscription: Subscription;
loginFailureCount: Subscription;
@ -57,6 +63,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {
waitingTime: number;
appUserInfo: AppUserInfo;
loginPw: string;
autologin: boolean;
useRememberMe: boolean;
useAutoLogin: boolean;
@ -85,7 +93,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
private protocolService: ProtocolService,
private localStorageService: LocalStorageService,
private sessionStorageService: SessionStorageService,
private appAuthenticationService: AppAuthenticationService
private externalApiService: ExternalApiService,
private appAuthenticationService: AppAuthenticationService,
private ngZone: NgZone
) {
this.useRememberMe =
environment.productConfig.authentication.rememberMe.use;
@ -95,6 +105,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
if (!!this.appUserInfo) {
this.autologin = this.appUserInfo.settings.general.autoLogin || false;
}
this.rotateInfomationIndex =
new Date().getTime() % this.rotateInfomation.length;
@ -113,15 +126,36 @@ export class LoginPageComponent implements OnInit, OnDestroy {
this.defatulLoginBtnText = this.translateService.instant('accounts.login');
this.defatulWaitingTime = 5 * 60; // sec
this.store.dispatch(
CompanyStore.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));
} else {
this.store.dispatch(
CompanyStore.companyListFailure({ error: 'Failed' })
);
}
}),
catchError(error => {
console.log('network disconnected', error);
return of();
})
)
.subscribe();
this.companyList$ = this.store.pipe(
select(AppStore.SettingSelector.CompanySelector.companyList)
);
this.companyListSubscription = this.store
.pipe(
select(AppStore.SettingSelector.CompanySelector.companyList),
map(companyList => {
this.companyList = companyList;
})
)
.subscribe();
this.loginFailureCount = this.store
.pipe(
@ -189,6 +223,56 @@ export class LoginPageComponent implements OnInit, OnDestroy {
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) {
let msg = this.translateService.instant('accounts.results.doLogout');
switch (personLogout.reasonCode) {
@ -234,6 +318,9 @@ export class LoginPageComponent implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
if (!!this.companyListSubscription) {
this.companyListSubscription.unsubscribe();
}
if (!!this.loginFailureCount) {
this.loginFailureCount.unsubscribe();
}
@ -268,23 +355,83 @@ export class LoginPageComponent implements OnInit, OnDestroy {
autoLogin: boolean;
notValid: () => void;
}) {
this.sessionStorageService.remove(KEY_LOGOUT_INFO);
this.loginBtnEnable = false;
setTimeout(() => {
this.loginBtnEnable = true;
}, 30 * 1000);
this.store.dispatch(
AuthenticationStore.webLogin({
loginInfo: {
companyCode: value.companyCode,
companyGroupType: 'C',
loginId: value.loginId,
loginPw: value.loginPw
},
rememberMe: value.rememberMe,
autoLogin: value.autoLogin
})
);
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(
AuthenticationStore.webLogin({
loginInfo: {
companyCode: value.companyCode,
companyGroupType: 'C',
loginId: value.loginId,
loginPw: value.loginPw
},
rememberMe: value.rememberMe,
autoLogin: value.autoLogin
})
);
}
}
onClickNoti() {

View File

@ -79,6 +79,12 @@ export class AppAuthenticationService {
}
}
};
if (!!environment.productConfig.defaultSettings.general.autoLaunch) {
this.nativeService.changeAutoLaunch(
environment.productConfig.defaultSettings.general.autoLaunch
);
}
}
appUserInfo = {
@ -118,21 +124,23 @@ export class AppAuthenticationService {
environment.customConfig.appKey
);
appUserInfo = {
...appUserInfo,
settings: {
...appUserInfo.settings,
general: {
...appUserInfo.settings.general,
autoLogin: false
if (!!appUserInfo) {
appUserInfo = {
...appUserInfo,
settings: {
...appUserInfo.settings,
general: {
...appUserInfo.settings.general,
autoLogin: false
}
}
}
};
};
this.localStorageService.encSet<AppUserInfo>(
KEY_APP_USER_INFO,
appUserInfo,
environment.customConfig.appKey
);
this.localStorageService.encSet<AppUserInfo>(
KEY_APP_USER_INFO,
appUserInfo,
environment.customConfig.appKey
);
}
}
}

View File

@ -1,7 +1,6 @@
import { delGroupSuccess, buddy2 } from './../store/messenger/sync/actions';
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';
@ -95,7 +94,11 @@ import {
NotificationType,
WindowState
} from '@ucap-webmessenger/native';
import { StringUtil, DialogService } from '@ucap-webmessenger/ui';
import {
StringUtil,
DialogService,
TranslateService as UcapTranslateService
} from '@ucap-webmessenger/ui';
import {
UmgProtocolService,
SSVC_TYPE_UMG_NOTI,
@ -110,10 +113,10 @@ import {
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { environment } from '../../environments/environment';
import { NotificationMethod } from '@ucap-webmessenger/core';
import { NotificationMethod, LocaleCode } from '@ucap-webmessenger/core';
import { Dictionary } from '@ngrx/entity';
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 { deleteMessageSuccess } from '@app/store/messenger/message';
import { ServerErrorCode } from '@ucap-webmessenger/protocol';
@ -130,6 +133,11 @@ import {
ChatSetting
} from '@ucap-webmessenger/ui-settings';
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()
export class AppNotificationService {
@ -140,8 +148,10 @@ export class AppNotificationService {
private roomProtocolService: RoomProtocolService,
private groupProtocolService: GroupProtocolService,
private buddyProtocolService: BuddyProtocolService,
private queryProtocolService: QueryProtocolService,
private statusProtocolService: StatusProtocolService,
private translateService: TranslateService,
private ucapTranslateService: UcapTranslateService,
private optionProtocolService: OptionProtocolService,
private umgProtocolService: UmgProtocolService,
private localStorageService: LocalStorageService,
@ -204,150 +214,262 @@ export class AppNotificationService {
this.store.pipe(
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(
select(
(state: any) =>
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]) => {
switch (notiOrRes.SSVC_TYPE) {
case SSVC_TYPE_EVENT_SEND_RES:
case SSVC_TYPE_EVENT_SEND_NOTI:
{
const noti = notiOrRes as SendNotification;
this.logger.debug(
'Notification::eventProtocolService::SendNotification',
noti
);
this.store.dispatch(
EventStore.sendNotification({
tap(
([
notiOrRes,
curRoomInfo,
curRoomUserInfo,
roomList,
buddyList,
roomUserShorts
]) => {
switch (notiOrRes.SSVC_TYPE) {
case SSVC_TYPE_EVENT_SEND_RES:
case SSVC_TYPE_EVENT_SEND_NOTI:
{
const noti = notiOrRes as SendNotification;
this.logger.debug(
'Notification::eventProtocolService::SendNotification',
noti
})
);
);
// notification..
if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) {
let doNoti = true;
this.store.dispatch(
EventStore.sendNotification({
noti
})
);
// 방별 알림이 꺼져 있으면 노티 안함.
if (
!!roomList[noti.roomSeq] &&
!roomList[noti.roomSeq].receiveAlarm
) {
doNoti = false;
}
// notification..
if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) {
let doNoti = true;
const windowState = this.nativeService.getWindowState();
const windowState = this.nativeService.getWindowState();
// 현재 열려 있는 방일경우 노티 안함.
if (
!!curRoomInfo &&
!!curRoomInfo.roomSeq &&
curRoomInfo.roomSeq === noti.roomSeq &&
!!windowState &&
windowState !== WindowState.Minimized &&
windowState !== WindowState.Hidden
) {
doNoti = false;
}
// 현재 열려 있는 방일경우 노티 안함.
if (
!!curRoomInfo &&
!!curRoomInfo.roomSeq &&
curRoomInfo.roomSeq === noti.roomSeq &&
!!windowState &&
windowState.windowState !== WindowState.Minimized &&
windowState.windowState !== WindowState.Hidden
) {
doNoti = false;
}
if (doNoti) {
const appUserInfo = this.localStorageService.encGet<
AppUserInfo
>(KEY_APP_USER_INFO, environment.customConfig.appKey);
// // 포커스 아웃일때 무조건 노티.
// // Case 1 : 단순 포커스 아웃.
// // Case 2 : hidden 시 포커스 인 상태이지만 위에서 필터링 됨.
// console.log(windowState);
// if (
// windowState.windowFocusState !==
// ElectronBrowserWindowChannel.Focus
// ) {
// doNoti = true;
// }
if (appUserInfo.settings.notification.use) {
if (
appUserInfo.settings.notification.method ===
NotificationMethod.Sound
) {
const audio = new Audio(
'assets/sounds/messageAlarm.mp3'
);
audio.play();
} else {
const contents = StringUtil.convertFinalEventMessage(
noti.eventType,
noti.info.sentMessageJson
);
// 방별 알림이 꺼져 있으면 노티 안함. > 우선순위 최상위.
if (
!!roomList[noti.roomSeq] &&
!roomList[noti.roomSeq].receiveAlarm
) {
doNoti = false;
}
if (!!contents) {
const notiReq: NotificationRequest = {
type: NotificationType.Event,
seq: noti.roomSeq,
title: this.translateService.instant(
'notification.titleChatEventArrived'
),
contents,
image: '',
useSound: [
NotificationMethod.Sound,
NotificationMethod.SoundAndAlert
].some(
n =>
n === appUserInfo.settings.notification.method
)
? true
: false,
displayTime:
appUserInfo.settings.notification
.alertExposureTime * 1000
};
this.nativeService.notify(notiReq);
if (doNoti) {
const appUserInfo = this.localStorageService.encGet<
AppUserInfo
>(KEY_APP_USER_INFO, environment.customConfig.appKey);
if (appUserInfo.settings.notification.use) {
if (
appUserInfo.settings.notification.method ===
NotificationMethod.Sound
) {
const audio = new Audio(
'assets/sounds/messageAlarm.mp3'
);
audio.play();
} else {
const contents = StringUtil.convertFinalEventMessage(
noti.eventType,
noti.info.sentMessageJson
);
if (!!contents) {
const notiReq: NotificationRequest = {
type: NotificationType.Event,
seq: noti.roomSeq,
title: this.translateService.instant(
'notification.titleChatEventArrived'
),
contents,
image: '',
useSound: [
NotificationMethod.Sound,
NotificationMethod.SoundAndAlert
].some(
n =>
n === appUserInfo.settings.notification.method
)
? true
: false,
displayTime:
appUserInfo.settings.notification
.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);
}
}
}
}
}
}
}
break;
case SSVC_TYPE_EVENT_READ_RES:
case SSVC_TYPE_EVENT_READ_NOTI:
{
// 대화방 unread count 처리.
const noti = notiOrRes as ReadNotification;
this.logger.debug(
'Notification::eventProtocolService::ReadNotification',
noti
);
this.store.dispatch(EventStore.readNotification(noti));
}
break;
case SSVC_TYPE_EVENT_CANCEL_NOTI:
{
const noti = notiOrRes as CancelNotification;
this.logger.debug(
'Notification::eventProtocolService::CancelNotification',
noti
);
this.store.dispatch(
EventStore.cancelNotification({
break;
case SSVC_TYPE_EVENT_READ_RES:
case SSVC_TYPE_EVENT_READ_NOTI:
{
// 대화방 unread count 처리.
const noti = notiOrRes as ReadNotification;
this.logger.debug(
'Notification::eventProtocolService::ReadNotification',
noti
})
);
}
break;
case SSVC_TYPE_EVENT_DEL_RES:
{
const noti = notiOrRes as DelNotification;
this.logger.debug(
'Notification::eventProtocolService::DelNotification',
noti
);
this.store.dispatch(
EventStore.delNotification({
);
this.store.dispatch(EventStore.readNotification(noti));
}
break;
case SSVC_TYPE_EVENT_CANCEL_NOTI:
{
const noti = notiOrRes as CancelNotification;
this.logger.debug(
'Notification::eventProtocolService::CancelNotification',
noti
})
);
}
break;
default:
break;
);
this.store.dispatch(
EventStore.cancelNotification({
noti
})
);
}
break;
case SSVC_TYPE_EVENT_DEL_RES:
{
const noti = notiOrRes as DelNotification;
this.logger.debug(
'Notification::eventProtocolService::DelNotification',
noti
);
this.store.dispatch(
EventStore.delNotification({
noti
})
);
}
break;
default:
break;
}
}
})
)
)
.subscribe();
this.infoProtocolService.notification$

View File

@ -76,7 +76,11 @@ import {
ServiceProtocolService,
UserPasswordSetResponse
} 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 { DaesangCipherService, WebLinkType } from '@ucap-webmessenger/daesang';
import { TranslateService } from '@ngx-translate/core';
@ -84,44 +88,130 @@ import {
InfoProtocolService,
UserResponse
} from '@ucap-webmessenger/protocol-info';
import { StatusCode } from '@ucap-webmessenger/api';
@Injectable()
export class Effects {
webLogin$ = createEffect(() =>
this.actions$.pipe(
ofType(webLogin),
map(action => action),
exhaustMap(
(params: {
loginInfo: LoginInfo;
rememberMe: boolean;
autoLogin: boolean;
}) =>
this.piService
.login2({
loginId: params.loginInfo.loginId,
loginPw: params.loginInfo.loginPw,
companyCode: params.loginInfo.companyCode
})
.pipe(
map((res: Login2Response) => {
if ('success' !== res.status.toLowerCase()) {
this.store.dispatch(increaseLoginFailCount({}));
return webLoginFailure({ error: 'Failed' });
} else {
this.store.dispatch(initialLoginFailCount({}));
return webLoginSuccess({
loginInfo: params.loginInfo,
rememberMe: params.rememberMe,
autoLogin: params.autoLogin,
login2Response: res
});
}
}),
catchError(error => of(webLoginFailure({ error })))
)
)
)
retryCount = 0;
retryInterval = 3000; // ms
maxRetryCount = (1 * 60 * 1000) / this.retryInterval; // 20 count due to 1 min.
webLogin$ = createEffect(
() =>
this.actions$.pipe(
ofType(webLogin),
map(action => action),
exhaustMap(
(params: {
loginInfo: LoginInfo;
rememberMe: boolean;
autoLogin: boolean;
}) => {
const selfParam = params;
return this.piService
.login2({
loginId: params.loginInfo.loginId,
loginPw: params.loginInfo.loginPw,
companyCode: params.loginInfo.companyCode
})
.pipe(
map((res: Login2Response) => {
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(webLoginFailure({ error: 'Failed' }));
}
} else {
this.store.dispatch(initialLoginFailCount({}));
this.store.dispatch(
webLoginSuccess({
loginInfo: params.loginInfo,
rememberMe: params.rememberMe,
autoLogin: params.autoLogin,
login2Response: res
})
);
}
}),
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(
@ -500,6 +590,7 @@ export class Effects {
private sessionStorageService: SessionStorageService,
private piService: PiService,
private appAuthenticationService: AppAuthenticationService,
private externalApiService: ExternalApiService,
private protocolService: ProtocolService,
private authenticationProtocolService: AuthenticationProtocolService,
private infoProtocolService: InfoProtocolService,

View File

@ -925,20 +925,37 @@ 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 .
*/
if (action.info.type === EventType.File) {
// File 정보 수집.
this.store.dispatch(
fileInfo({
req: {
roomSeq: action.roomSeq,
// { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌
type: FileType.All
}
})
);
}
} else {
if (!roomInfo || roomInfo.roomSeq !== action.roomSeq) {
if (!!trgtRoomInfos && !!trgtRoomInfos[action.roomSeq]) {
// 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({
@ -950,19 +967,6 @@ export class Effects {
}
}
if (action.info.type === EventType.File) {
// File 정보 수집.
this.store.dispatch(
fileInfo({
req: {
roomSeq: action.roomSeq,
// { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌
type: FileType.All
}
})
);
}
// 대화 > 리스트 :: finalEventMessage refresh
this.store.dispatch(ChatStore.newEventMessage(action));
})

View File

@ -188,3 +188,8 @@ export const exitFailure = createAction(
'[Messenger::Room] Exit Failure',
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 { NGXLogger } from 'ngx-logger';
import { environment } from '../../../../environments/environment';
import { of } from 'rxjs';
import {
tap,
@ -68,7 +70,8 @@ import {
exitForcingFailure,
exitForcingSuccess,
exitNotificationOthers,
clearRoomUser
clearRoomUser,
syncRoomRefreshByInvite
} from './actions';
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { LoginInfo, KEY_LOGIN_INFO, KEY_VER_INFO } from '@app/types';
@ -79,6 +82,8 @@ import {
AlertDialogData,
AlertDialogResult
} from '@ucap-webmessenger/ui';
import { ActivatedRouteSnapshot } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
@Injectable()
export class Effects {
@ -285,6 +290,29 @@ export class Effects {
)
),
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) {
// Re Open
return this.roomProtocolService.open(action.req).pipe(
@ -371,10 +399,11 @@ export class Effects {
)
),
tap(([action, roomInfo]) => {
const loginInfo = this.sessionStorageService.get<LoginInfo>(
KEY_LOGIN_INFO
);
if (!!roomInfo && roomInfo.roomSeq === action.noti.roomSeq) {
const loginInfo = this.sessionStorageService.get<LoginInfo>(
KEY_LOGIN_INFO
);
this.store.dispatch(
info({
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 roomProtocolService: RoomProtocolService,
private sessionStorageService: SessionStorageService,
private translateService: TranslateService,
private dialogService: DialogService,
private logger: NGXLogger
) {}

View File

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

View File

@ -540,6 +540,62 @@ export class Effects {
},
{ 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(() =>

View File

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

View File

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

View File

@ -45,7 +45,9 @@
"failToChangePassword": "Failed to change password.",
"loginFailed": "Failed to login",
"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": {
@ -57,6 +59,7 @@
"removeBuddy": "Remove a buddy",
"remoteSupport": "Remote support",
"fieldCompany": "Company",
"fieldEmployeeNumber": "Employee Number",
"fieldResponsibilities": "Responsibilities",
"fieldWorkplace": "Workplace",
"fieldJob": "Job",
@ -388,7 +391,9 @@
"label": "Update"
},
"notification": {
"titleChatEventArrivedByUser": "A Message of chat from {{userInfo}}.",
"titleChatEventArrived": "A message of chat has arrived.",
"titleMessageArrivedByUser": "A Message from {{userInfo}}.",
"titleMessageArrived": "A message has arrived."
},
"common": {

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@ import { Injectable } from '@angular/core';
import { TranslateLoaderService } from '../translate/electron-loader';
import { TranslateLoader } from '@ngx-translate/core';
import { StatusCode } from '@ucap-webmessenger/core';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
@Injectable({
providedIn: 'root'
@ -65,6 +66,10 @@ export class ElectronNativeService implements NativeService {
private backgroundCheckForUpdatesSubject: Subject<UpdateInfo> | null = null;
private backgroundCheckForUpdates$: Observable<UpdateInfo> | null = null;
private windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
type(): NativeType {
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()) {
return WindowState.Hidden;
windowState = WindowState.Hidden;
} else if (remote.getCurrentWindow().isMinimized()) {
return WindowState.Minimized;
windowState = WindowState.Minimized;
} else if (remote.getCurrentWindow().isNormal()) {
return WindowState.Normal;
windowState = WindowState.Normal;
} else if (remote.getCurrentWindow().isMaximized()) {
return WindowState.Maximized;
windowState = WindowState.Maximized;
} else if (remote.getCurrentWindow().isFullScreen()) {
return WindowState.FullScreen;
} else {
return WindowState.Normal;
windowState = WindowState.FullScreen;
}
return {
windowState,
windowFocusState: this.windowFocusState
};
}
appExit(): void {
this.ipcRenderer.send(AppChannel.Exit);
}
appLogging(error: any): void {
this.ipcRenderer.send(AppChannel.Logging, error);
}
zoomTo(factor: number): Promise<number> {
return new Promise<number>((resolve, reject) => {
@ -529,5 +546,17 @@ export class ElectronNativeService implements NativeService {
this.shell = (window as any).require('electron').shell;
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 {
Changed = 'UCAP::windowState::windowStateChanged'
Changed = 'UCAP::windowState::windowStateChanged',
FocuseChanged = 'UCAP::windowState::windowFocusStateChanged'
}
export enum IdleStateChannel {
@ -61,7 +62,8 @@ export enum ClipboardChannel {
}
export enum AppChannel {
Exit = 'UCAP::app::exit'
Exit = 'UCAP::app::exit',
Logging = 'UCAP::app::logging'
}
export enum ExternalChannel {

View File

@ -7,6 +7,7 @@ import { TranslateLoader } from '@ngx-translate/core';
import { StatusCode } from '@ucap-webmessenger/core';
import { UpdateInfo, UpdateCheckConfig } from '../models/update-info';
import { NativeType } from '../types/native.type';
import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core';
export type NativePathName =
| 'home'
@ -73,9 +74,15 @@ export abstract class NativeService {
abstract windowClose(): void;
abstract windowMinimize(): void;
abstract windowMaximize(): void;
abstract getWindowState(): WindowState;
abstract getWindowState(): {
windowState: WindowState;
windowFocusState:
| ElectronBrowserWindowChannel.Focus
| ElectronBrowserWindowChannel.Blur;
};
abstract zoomTo(factor: number): Promise<number>;
abstract appExit(): void;
abstract appLogging(error: any): void;
abstract idleStateChanged(): Observable<WindowIdle>;
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 { HttpParams, HttpParameterCodec } from '@angular/common/http';
export interface Login2Request extends PIRequest {
companyCode: string;
@ -26,7 +32,14 @@ const login2EncodeMap = {
};
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) => {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,9 +11,12 @@
<span class="bg-accent-dark">{{ 'chat.sentDate' | translate }}</span>
{{ message.sentMessageJson.postDate | ucapDate: 'YYYY.MM.DD a hh:mm' }}
</li>
<li class="event-content">
{{ message.sentMessageJson.content }}
</li>
<li
class="event-content"
[innerHTML]="
message.sentMessageJson.content | ucapSafeHtml | linefeedtohtml | linky
"
></li>
</ul>
<!-- <div class="btn-box">
<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 { UCAP_NATIVE_SERVICE, NativeService } from '@ucap-webmessenger/native';
@Component({
selector: 'ucap-chat-message-box-allim',
templateUrl: './allim.component.html',
styleUrls: ['./allim.component.scss']
})
export class AllimComponent implements OnInit {
export class AllimComponent implements OnInit, AfterViewInit {
@Input()
message: Info<AllimEventJson>;
constructor() {}
constructor(
private elementRef: ElementRef,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService
) {}
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)"
[senderName]="getUserName(message.senderSeq)"
[senderGrade]="getUserGrade(message.senderSeq)"
[profileImageRoot]="profileImageRoot"
[profileImage]="getUserProfile(message.senderSeq)"
[roomInfo]="roomInfo"

View File

@ -322,6 +322,19 @@ export class MessagesComponent implements OnInit, OnDestroy {
}
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 {
if (!this.userInfos) {
return '';

View File

@ -1,7 +1,11 @@
<mat-card class="example-card profile mat-elevation-z">
<mat-card-header>
<div class="profile-img">
<div class="profile-img-mask">
<div
class="profile-img-mask"
(click)="onClickProfileImageView()"
style="cursor: pointer;"
>
<img
ucapImage
[base]="profileImageRoot"
@ -24,7 +28,26 @@
>
<i class="mid mdi-camera"></i>
</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
*ngIf="getWorkstatus(userInfo).length > 0"
class="work-status"
@ -180,6 +203,12 @@
<dt class="division">{{ 'profile.fieldCompany' | translate }}</dt>
<dd>{{ userInfo.companyName | ucapStringEmptycheck }}</dd>
</li>
<li class="employeeNum">
<dt class="division">
{{ 'profile.fieldEmployeeNumber' | translate }}
</dt>
<dd>{{ userInfo.employeeNum | slice: 2 }}</dd>
</li>
<li class="deptName">
<dt class="division">{{ 'search.fieldDeptartment' | translate }}</dt>
<dd>{{ userInfo | ucapTranslate: 'deptName' }}</dd>
@ -379,6 +408,31 @@
}}</span>
</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">
<button
mat-mini-fab

View File

@ -74,6 +74,7 @@ $login-max-height: 800px;
.work-status {
display: inline-flex;
margin-left: 20px;
height: 24px;
border: 1px solid #ffffff;
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 {
position: relative;
display: flex;

View File

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

View File

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

View File

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

View File

@ -44,6 +44,31 @@
/>
</svg>
</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>
<button
mat-icon-button
@ -94,7 +119,7 @@
<div class="guide-msg">
{{ 'common.file.errors.noPreview' | translate }}
</div>
<div>
<div class="btn-group">
<button
colori
mat-raised-button
@ -103,6 +128,14 @@
>
{{ 'common.file.download' | translate }}
</button>
<button
colori
mat-raised-button
aria-label=""
(click)="onClickSaveAs()"
>
{{ 'common.file.saveAs' | translate }}
</button>
</div>
</div>
</div>

View File

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

View File

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

View File

@ -17,10 +17,21 @@
<circle cx="8.5" cy="8.5" r="1.5" />
<path d="M20.4 14.5L16 10 4 20" />
</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>
<button
*ngIf="!imageOnly"
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomReset' | translate }}"
@ -47,6 +58,7 @@
</svg>
</button>
<button
*ngIf="!imageOnly"
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomOut' | translate }}"
@ -72,6 +84,7 @@
</svg>
</button>
<button
*ngIf="!imageOnly"
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.messages.zoomIn' | translate }}"
@ -98,6 +111,7 @@
</svg>
</button>
<button
*ngIf="!imageOnly"
mat-icon-button
class="ucap-image-viewer-action"
matTooltip="{{ 'common.file.download' | translate }}"
@ -122,6 +136,32 @@
/>
</svg>
</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>
<button
mat-icon-button
@ -166,14 +206,29 @@
fxFlexFill
fxLayoutAlign="center center"
>
<img
#downloadImage
*ngIf="fileDownloadUrl"
[src]="fileDownloadUrl"
[style.width]="'auto'"
[style.height]="imageHeight + 'px'"
(load)="onLoadFileDownloadUrl(downloadImage)"
/>
<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
#downloadImage
*ngIf="fileDownloadUrl"
[src]="fileDownloadUrl"
[style.width]="'auto'"
[style.height]="imageHeight + 'px'"
(load)="onLoadFileDownloadUrl(downloadImage)"
/>
</ng-template>
</div>
</div>
</div>

View File

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

View File

@ -28,6 +28,31 @@
/>
</svg>
</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>
<button
mat-icon-button

View File

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

View File

@ -49,6 +49,31 @@
/>
</svg>
</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>
<button
mat-icon-button

View File

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

View File

@ -125,7 +125,7 @@
</th>
<td mat-cell *matCellDef="let element" class="lineNumber">
<div class="lineNumber">
{{ element.lineNumber }}
{{ element.lineNumber | ucapStringFormatterPhone }}
</div>
</td>
</ng-container>
@ -140,7 +140,7 @@
</th>
<td mat-cell *matCellDef="let element" class="hpNumber">
<div class="hpNumber">
{{ element.hpNumber }}
{{ element.hpNumber | ucapStringFormatterPhone }}
</div>
</td>
</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/image.directive';
export * from './lib/models/image-only-data-info';
export * from './lib/services/bottom-sheet.service';
export * from './lib/services/clipboard.service';
export * from './lib/services/dialog.service';