This commit is contained in:
leejinho 2020-01-14 08:28:02 +09:00
commit 20110f2c2a
39 changed files with 685 additions and 74 deletions

View File

@ -5,7 +5,8 @@ import {
Tray, Tray,
Menu, Menu,
shell, shell,
dialog dialog,
webFrame
} from 'electron'; } from 'electron';
import path from 'path'; import path from 'path';
import fse from 'fs-extra'; import fse from 'fs-extra';
@ -396,6 +397,13 @@ ipcMain.on(
} }
); );
ipcMain.on(
MessengerChannel.GetVersionInfo,
(event: IpcMainEvent, ...args: any[]) => {
event.returnValue = app.getVersion();
}
);
ipcMain.on( ipcMain.on(
MessengerChannel.ChangeAutoLaunch, MessengerChannel.ChangeAutoLaunch,
(event: IpcMainEvent, ...args: any[]) => { (event: IpcMainEvent, ...args: any[]) => {

View File

@ -1,6 +1,6 @@
{ {
"name": "ucap-webmessenger", "name": "ucap-webmessenger",
"version": "0.0.2", "version": "0.0.3",
"author": { "author": {
"name": "LG CNS", "name": "LG CNS",
"email": "lgucap@lgcns.com" "email": "lgucap@lgcns.com"

View File

@ -168,7 +168,7 @@
(click)="onClickProfileContextMenu('REGISTER_FAVORITE', userInfo)" (click)="onClickProfileContextMenu('REGISTER_FAVORITE', userInfo)"
> >
{{ {{
(userInfo.isFavorit ? 'group.unfavorite' : 'group.favorite') | translate (userInfo.isFavorit ? 'group.unfavorite' : 'group.favorit') | translate
}} }}
</button> </button>
<button mat-menu-item (click)="onClickProfileContextMenu('CHAT', userInfo)"> <button mat-menu-item (click)="onClickProfileContextMenu('CHAT', userInfo)">

View File

@ -130,7 +130,7 @@
'common.file.errors.expired' | translate 'common.file.errors.expired' | translate
}}</span> }}</span>
<button <button
mat-stroked-button mat-flat-button
class="mat-primary" class="mat-primary"
(click)="downloadAttachFileAll()" (click)="downloadAttachFileAll()"
> >
@ -143,7 +143,8 @@
<div> <div>
<span class="mdi" [ngClass]="getFileStatusIcon(file)"></span> <span class="mdi" [ngClass]="getFileStatusIcon(file)"></span>
<span>{{ file.resContent }}</span> <span>{{ file.resContent }}</span>
<span>{{ file.resSize | ucapBytes }}</span> </div>
<span class="file-size">{{ file.resSize | ucapBytes }}</span>
<a> <a>
<span <span
class="mdi mdi-download" class="mdi mdi-download"
@ -151,7 +152,6 @@
(click)="downloadAttachFileSingle(file)" (click)="downloadAttachFileSingle(file)"
></span> ></span>
</a> </a>
</div>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -109,5 +109,42 @@
.ps { .ps {
align-items: flex-start; align-items: flex-start;
} }
.attachFile {
border-top: 1px solid #dddddd;
li {
display: flex;
flex-flow: row;
line-height: 2em;
flex: 1 1 auto;
.file-name {
display: inline-flex;
flex-flow: row;
flex: 1 1 auto;
border: 1px solid red;
white-space: nowrap;
word-wrap: normal;
overflow: hidden;
margin-right: 10px;
span:last-child {
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: calc(100% - 40px);
}
}
.file-size {
display: flex;
margin-left: auto;
align-self: flex-end;
flex: 0 0 auto;
}
a {
display: inline-block;
width: 20px;
height: 100%;
margin-left: 10px;
}
}
}
} }
} }

View File

@ -281,7 +281,7 @@
(click)="onClickZoomOut($event)" (click)="onClickZoomOut($event)"
> >
축소</button 축소</button
><span class="set-size">100%</span ><span class="set-size" (click)="onClickZoomLabel($event)">{{ zoom }}%</span
><button ><button
mat-menu-item mat-menu-item
class="zoom plus-square" class="zoom plus-square"
@ -306,6 +306,12 @@
</button> </button>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="setting">
<button mat-menu-item [matMenuTriggerFor]="informationMenu">
{{ 'information.label' | translate }}
</button>
</div>
<mat-divider></mat-divider>
<div class="setting"> <div class="setting">
<button mat-menu-item (click)="onClickLogout()"> <button mat-menu-item (click)="onClickLogout()">
{{ 'accounts.logout' | translate }} {{ 'accounts.logout' | translate }}
@ -334,35 +340,71 @@
<div class="setting"> <div class="setting">
<button mat-menu-item (click)="onClickStatusBusy($event, 1)"> <button mat-menu-item (click)="onClickStatusBusy($event, 1)">
<span class="presence pcOther"> </span> <span class="presence pcOther"> </span>
{{ loginRes?.statusMessage1 }} <ucap-inline-edit-input
(apply)="
$event.stopPropagation();
onApplyStatusMessage(1, statusMessage1.value)
"
(edit)="$event.stopPropagation()"
(cancel)="$event.stopPropagation()"
>
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage1 }}</span>
<span ucapInlineEditInput="edit"
><input
matInput
#statusMessage1
type="text"
[value]="loginRes?.statusMessage1"
(click)="$event.stopPropagation()"
/></span>
</ucap-inline-edit-input>
</button> </button>
<button
mat-menu-item
class="edit"
(click)="onClickChangeStatusBusy($event, 1)"
></button>
</div> </div>
<div class="setting"> <div class="setting">
<button mat-menu-item (click)="onClickStatusBusy($event, 2)"> <button mat-menu-item (click)="onClickStatusBusy($event, 2)">
<span class="presence pcOther"> </span> <span class="presence pcOther"> </span>
{{ loginRes?.statusMessage2 }} <ucap-inline-edit-input
(apply)="
$event.stopPropagation();
onApplyStatusMessage(2, statusMessage2.value)
"
(edit)="$event.stopPropagation()"
(cancel)="$event.stopPropagation()"
>
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage2 }}</span>
<span ucapInlineEditInput="edit"
><input
matInput
#statusMessage2
type="text"
[value]="loginRes?.statusMessage2"
(click)="$event.stopPropagation()"
/></span>
</ucap-inline-edit-input>
</button> </button>
<button
mat-menu-item
class="edit"
(click)="onClickChangeStatusBusy($event, 2)"
></button>
</div> </div>
<div class="setting"> <div class="setting">
<button mat-menu-item (click)="onClickStatusBusy($event, 3)"> <button mat-menu-item (click)="onClickStatusBusy($event, 3)">
<span class="presence pcOther"> </span> <span class="presence pcOther"> </span>
{{ loginRes?.statusMessage3 }} <ucap-inline-edit-input
(apply)="
$event.stopPropagation();
onApplyStatusMessage(3, statusMessage3.value)
"
(edit)="$event.stopPropagation()"
(cancel)="$event.stopPropagation()"
>
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage3 }}</span>
<span ucapInlineEditInput="edit"
><input
matInput
#statusMessage3
type="text"
[value]="loginRes?.statusMessage3"
(click)="$event.stopPropagation()"
/></span>
</ucap-inline-edit-input>
</button> </button>
<button
mat-menu-item
class="edit"
(click)="onClickChangeStatusBusy($event, 3)"
></button>
</div> </div>
</mat-menu> </mat-menu>
@ -370,28 +412,17 @@
<div mat-menu-item (click)="$event.stopPropagation()"> <div mat-menu-item (click)="$event.stopPropagation()">
{{ 'presence.settingOfAwayTime' | translate }} {{ 'presence.settingOfAwayTime' | translate }}
</div> </div>
<div mat-menu-item> <mat-radio-group aria-label="Select an option" [value]="myIdleCheckTime">
<mat-radio-button <div mat-menu-item *ngFor="let awayTime of awayTimeList">
value="10" <mat-radio-button [value]="awayTime" (change)="onChangeAwayTime($event)">
[checked]="myIdleCheckTime === 10" {{ awayTime }}{{ 'common.units.minute' | translate }}</mat-radio-button
(change)="onChangeAwayTime($event)"
>10{{ 'common.units.minute' | translate }}</mat-radio-button
> >
</div> </div>
</mat-radio-group>
</mat-menu>
<mat-menu #informationMenu="matMenu">
<div mat-menu-item> <div mat-menu-item>
<mat-radio-button {{ 'information.version' | translate }}: {{ appVersion }}
value="20"
[checked]="myIdleCheckTime === 20"
(change)="onChangeAwayTime($event)"
>20{{ 'common.units.minute' | translate }}</mat-radio-button
>
</div>
<div mat-menu-item>
<mat-radio-button
value="30"
[checked]="myIdleCheckTime === 30"
(change)="onChangeAwayTime($event)"
>30{{ 'common.units.minute' | translate }}</mat-radio-button
>
</div> </div>
</mat-menu> </mat-menu>

View File

@ -20,6 +20,7 @@ import * as ChatStore from '@app/store/messenger/chat';
import * as AuthenticationStore from '@app/store/account/authentication'; import * as AuthenticationStore from '@app/store/account/authentication';
import * as SettingsStore from '@app/store/messenger/settings'; import * as SettingsStore from '@app/store/messenger/settings';
import * as UpdateStore from '@app/store/setting/update'; import * as UpdateStore from '@app/store/setting/update';
import * as SettingNativeStore from '@app/store/setting/native';
import * as StatusStore from '@app/store/messenger/status'; import * as StatusStore from '@app/store/messenger/status';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
@ -58,6 +59,9 @@ import { DOCUMENT } from '@angular/common';
import { MatMenu, MatRadioChange } from '@angular/material'; import { MatMenu, MatRadioChange } from '@angular/material';
import { StatusCode, StatusType } from '@ucap-webmessenger/core'; import { StatusCode, StatusType } from '@ucap-webmessenger/core';
import { StatusInfo } from '@ucap-webmessenger/protocol-status'; import { StatusInfo } from '@ucap-webmessenger/protocol-status';
import { UserInfoUpdateType } from '@ucap-webmessenger/protocol-info';
const zoomFactors = [60, 70, 85, 100, 120, 145, 170, 200];
@Component({ @Component({
selector: 'app-layout-native-top-bar', selector: 'app-layout-native-top-bar',
@ -80,14 +84,21 @@ export class TopBarComponent implements OnInit, OnDestroy {
myIdleCheckTime: number; myIdleCheckTime: number;
myIdleCheckTimeSubscription: Subscription; myIdleCheckTimeSubscription: Subscription;
zoom: number;
zoomSubscription: Subscription;
loginInfo: LoginInfo; loginInfo: LoginInfo;
weblink: WebLink[] = []; weblink: WebLink[] = [];
webLinkBadgeMail = 0; webLinkBadgeMail = 0;
webLinkBadgePayment = 0; webLinkBadgePayment = 0;
appVersion: string;
WebLinkType = WebLinkType; WebLinkType = WebLinkType;
StatusCode = StatusCode; StatusCode = StatusCode;
readonly awayTimeList = [10, 20, 30];
@ViewChild('profileMenu', { static: true }) @ViewChild('profileMenu', { static: true })
profileMenu: MatMenu; profileMenu: MatMenu;
@ -127,35 +138,50 @@ export class TopBarComponent implements OnInit, OnDestroy {
.subscribe(); .subscribe();
this.myStatusSubscription = this.store this.myStatusSubscription = this.store
.pipe(select(AppStore.MessengerSelector.StatusSelector.selectedMyStatus)) .pipe(select(AppStore.MessengerSelector.StatusSelector.selectMyStatus))
.subscribe(myStatus => { .subscribe(myStatus => {
this.myStatus = myStatus; this.myStatus = myStatus;
}); });
this.myIdleCheckTimeSubscription = this.store this.myIdleCheckTimeSubscription = this.store
.pipe( .pipe(
select( select(AppStore.MessengerSelector.StatusSelector.selectMyIdleCheckTime)
AppStore.MessengerSelector.StatusSelector.selectedMyIdleCheckTime
)
) )
.subscribe(myIdleCheckTime => { .subscribe(myIdleCheckTime => {
this.myIdleCheckTime = myIdleCheckTime; this.myIdleCheckTime = myIdleCheckTime;
}); });
this.zoomSubscription = this.store
.pipe(select(AppStore.SettingSelector.NativeSelector.selectZoom))
.subscribe(zoom => {
this.zoom = zoom;
});
this.updateInfo$ = this.store.pipe( this.updateInfo$ = this.store.pipe(
select(AppStore.SettingSelector.UpdateSelector.updateInfo) select(AppStore.SettingSelector.UpdateSelector.updateInfo)
); );
this.nativeService.getVersionInfo().then(ver => {
this.appVersion = ver;
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.loginResSubscription) { if (!!this.loginResSubscription) {
this.loginResSubscription.unsubscribe(); this.loginResSubscription.unsubscribe();
this.loginResSubscription = undefined;
} }
if (!!this.myStatusSubscription) { if (!!this.myStatusSubscription) {
this.myStatusSubscription.unsubscribe(); this.myStatusSubscription.unsubscribe();
this.myStatusSubscription = undefined;
} }
if (!!this.myIdleCheckTimeSubscription) { if (!!this.myIdleCheckTimeSubscription) {
this.myIdleCheckTimeSubscription.unsubscribe(); this.myIdleCheckTimeSubscription.unsubscribe();
this.myIdleCheckTimeSubscription = undefined;
}
if (!!this.zoomSubscription) {
this.zoomSubscription.unsubscribe();
this.zoomSubscription = undefined;
} }
} }
@ -357,15 +383,27 @@ export class TopBarComponent implements OnInit, OnDestroy {
} }
onClickZoomOut(event: Event) { onClickZoomOut(event: Event) {
event.stopPropagation(); const i = zoomFactors.indexOf(this.zoom);
if (-1 === i || 0 === i) {
return;
}
this.document.body.style.zoom = '80%'; const zoom = zoomFactors[i - 1];
this.store.dispatch(SettingNativeStore.changeZoom({ zoom }));
}
onClickZoomLabel(event: Event) {
this.store.dispatch(SettingNativeStore.changeZoom({ zoom: 100 }));
} }
onClickZoomIn(event: Event) { onClickZoomIn(event: Event) {
event.stopPropagation(); const i = zoomFactors.indexOf(this.zoom);
if (-1 === i || zoomFactors.length - 1 === i) {
return;
}
this.document.body.style.zoom = '120%'; const zoom = zoomFactors[i + 1];
this.store.dispatch(SettingNativeStore.changeZoom({ zoom }));
} }
onClickRemoteSupport(event: Event) { onClickRemoteSupport(event: Event) {
@ -419,6 +457,32 @@ export class TopBarComponent implements OnInit, OnDestroy {
); );
} }
onApplyStatusMessage(index: number, statusMessage: string) {
this.logger.debug('StatusMessage', index, statusMessage);
let updateType: UserInfoUpdateType;
switch (index) {
case 1:
updateType = UserInfoUpdateType.StatusMessage1;
break;
case 2:
updateType = UserInfoUpdateType.StatusMessage2;
break;
case 3:
updateType = UserInfoUpdateType.StatusMessage3;
break;
default:
return;
}
this.store.dispatch(
AuthenticationStore.infoUser({
req: { type: updateType, info: statusMessage }
})
);
}
onClickChangeStatusBusy(event: Event, index: number) { onClickChangeStatusBusy(event: Event, index: number) {
event.stopPropagation(); event.stopPropagation();
} }

View File

@ -137,9 +137,7 @@ export class MainPageComponent implements OnInit, OnDestroy {
this.myIdleCheckTimeSubscription = this.store this.myIdleCheckTimeSubscription = this.store
.pipe( .pipe(
select( select(AppStore.MessengerSelector.StatusSelector.selectMyIdleCheckTime)
AppStore.MessengerSelector.StatusSelector.selectedMyIdleCheckTime
)
) )
.subscribe(checkTime => { .subscribe(checkTime => {
this.nativeService.changeLimitOfIdleState(checkTime); this.nativeService.changeLimitOfIdleState(checkTime);

View File

@ -58,6 +58,7 @@ import * as OptionStore from '@app/store/messenger/option';
import * as QueryStore from '@app/store/messenger/query'; import * as QueryStore from '@app/store/messenger/query';
import * as SyncStore from '@app/store/messenger/sync'; import * as SyncStore from '@app/store/messenger/sync';
import * as StatusStore from '@app/store/messenger/status'; import * as StatusStore from '@app/store/messenger/status';
import * as SettingInitStore from '@app/store/setting/init';
import { KEY_LOGIN_RES_INFO, KEY_VER_INFO } from '@app/types'; import { KEY_LOGIN_RES_INFO, KEY_VER_INFO } from '@app/types';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';

View File

@ -66,6 +66,7 @@ export class AppAuthenticationService {
if (!appUserInfo) { if (!appUserInfo) {
appUserInfo = { appUserInfo = {
idleCheckTime: 10, idleCheckTime: 10,
zoom: 100,
settings: { settings: {
...environment.productConfig.defaultSettings, ...environment.productConfig.defaultSettings,
chat: { chat: {

View File

@ -8,6 +8,7 @@ import {
UserPasswordSetRequest, UserPasswordSetRequest,
UserPasswordSetResponse UserPasswordSetResponse
} from '@ucap-webmessenger/protocol-service'; } from '@ucap-webmessenger/protocol-service';
import { UserRequest, UserResponse } from '@ucap-webmessenger/protocol-info';
export const webLogin = createAction( export const webLogin = createAction(
'[Account::Authentication] Web Login', '[Account::Authentication] Web Login',
@ -122,3 +123,20 @@ export const updateLoginRes = createAction(
loginRes: LoginResponse; loginRes: LoginResponse;
}>() }>()
); );
export const infoUser = createAction(
'[Account::Authentication] Info User',
props<{ req: UserRequest }>()
);
export const infoUserSuccess = createAction(
'[Account::Authentication] Info User Success',
props<{
res: UserResponse;
}>()
);
export const infoUserFailure = createAction(
'[Account::Authentication] Info User Failure',
props<{ error: any }>()
);

View File

@ -41,7 +41,10 @@ import {
logoutInitialize, logoutInitialize,
userPasswordSet, userPasswordSet,
userPasswordSetSuccess, userPasswordSetSuccess,
userPasswordSetFailure userPasswordSetFailure,
infoUser,
infoUserSuccess,
infoUserFailure
} from './actions'; } from './actions';
import { import {
LoginInfo, LoginInfo,
@ -77,6 +80,10 @@ import { DaesangUrlInfoResponse } from '@ucap-webmessenger/api-external';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type'; import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { DaesangCipherService, WebLinkType } from '@ucap-webmessenger/daesang'; import { DaesangCipherService, WebLinkType } from '@ucap-webmessenger/daesang';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {
InfoProtocolService,
UserResponse
} from '@ucap-webmessenger/protocol-info';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -482,6 +489,23 @@ export class Effects {
{ dispatch: false } { dispatch: false }
); );
infoUser$ = createEffect(() =>
this.actions$.pipe(
ofType(infoUser),
map(action => action.req),
exhaustMap(req =>
this.infoProtocolService.user(req).pipe(
map((res: UserResponse) => {
return infoUserSuccess({
res
});
}),
catchError(error => of(infoUserFailure({ error })))
)
)
)
);
constructor( constructor(
private actions$: Actions, private actions$: Actions,
private ngZone: NgZone, private ngZone: NgZone,
@ -493,6 +517,7 @@ export class Effects {
private appAuthenticationService: AppAuthenticationService, private appAuthenticationService: AppAuthenticationService,
private protocolService: ProtocolService, private protocolService: ProtocolService,
private authenticationProtocolService: AuthenticationProtocolService, private authenticationProtocolService: AuthenticationProtocolService,
private infoProtocolService: InfoProtocolService,
private serviceProtocolService: ServiceProtocolService, private serviceProtocolService: ServiceProtocolService,
private translateService: TranslateService, private translateService: TranslateService,
private dialogService: DialogService, private dialogService: DialogService,

View File

@ -6,8 +6,10 @@ import {
initialLoginFailCount, initialLoginFailCount,
logout, logout,
logoutInitialize, logoutInitialize,
updateLoginRes updateLoginRes,
infoUserSuccess
} from './actions'; } from './actions';
import { UserInfoUpdateType } from '@ucap-webmessenger/protocol-info';
export const reducer = createReducer( export const reducer = createReducer(
initialState, initialState,
@ -39,6 +41,57 @@ export const reducer = createReducer(
}; };
}), }),
on(infoUserSuccess, (state, action) => {
let loginRes = {
...state.loginRes
};
switch (action.res.type) {
case UserInfoUpdateType.Image:
loginRes = {
...loginRes
};
break;
case UserInfoUpdateType.Intro:
loginRes = {
...loginRes
};
break;
case UserInfoUpdateType.TelephoneVisible:
loginRes = {
...loginRes
};
break;
case UserInfoUpdateType.StatusMessage1:
loginRes = {
...loginRes,
statusMessage1: action.res.info
};
break;
case UserInfoUpdateType.StatusMessage2:
loginRes = {
...loginRes,
statusMessage2: action.res.info
};
break;
case UserInfoUpdateType.StatusMessage3:
loginRes = {
...loginRes,
statusMessage3: action.res.info
};
break;
default:
break;
}
return {
...state,
loginRes: {
...loginRes
}
};
}),
on(logoutInitialize, (state, action) => { on(logoutInitialize, (state, action) => {
return { return {
...initialState ...initialState

View File

@ -62,11 +62,8 @@ export function selectors<S>(selector: Selector<any, State>) {
ngeSelectEntitiesStatusBulkInfo, ngeSelectEntitiesStatusBulkInfo,
(_, entities) => (!!entities ? entities[userSeq] : undefined) (_, entities) => (!!entities ? entities[userSeq] : undefined)
), ),
selectedMyStatus: createSelector( selectMyStatus: createSelector(selector, (state: State) => state.myStatus),
selector, selectMyIdleCheckTime: createSelector(
(state: State) => state.myStatus
),
selectedMyIdleCheckTime: createSelector(
selector, selector,
(state: State) => state.myIdleCheckTime (state: State) => state.myIdleCheckTime
) )

View File

@ -5,19 +5,22 @@ import * as CompanyStore from './company';
import * as InitStore from './init'; import * as InitStore from './init';
import * as VersionInfoStore from './version-info'; import * as VersionInfoStore from './version-info';
import * as UpdateStore from './update'; import * as UpdateStore from './update';
import * as NativeStore from './native';
export interface State { export interface State {
company: CompanyStore.State; company: CompanyStore.State;
init: InitStore.State; init: InitStore.State;
versionInfo: VersionInfoStore.State; versionInfo: VersionInfoStore.State;
update: UpdateStore.State; update: UpdateStore.State;
native: NativeStore.State;
} }
export const effects: Type<any>[] = [ export const effects: Type<any>[] = [
CompanyStore.Effects, CompanyStore.Effects,
InitStore.Effects, InitStore.Effects,
VersionInfoStore.Effects, VersionInfoStore.Effects,
UpdateStore.Effects UpdateStore.Effects,
NativeStore.Effects
]; ];
export function reducers(state: State | undefined, action: Action) { export function reducers(state: State | undefined, action: Action) {
@ -25,7 +28,8 @@ export function reducers(state: State | undefined, action: Action) {
company: CompanyStore.reducer, company: CompanyStore.reducer,
init: InitStore.reducer, init: InitStore.reducer,
versionInfo: VersionInfoStore.reducer, versionInfo: VersionInfoStore.reducer,
update: UpdateStore.reducer update: UpdateStore.reducer,
native: NativeStore.reducer
})(state, action); })(state, action);
} }
@ -42,6 +46,9 @@ export function selectors<S>(selector: Selector<any, State>) {
), ),
UpdateSelector: UpdateStore.selectors( UpdateSelector: UpdateStore.selectors(
createSelector(selector, (state: State) => state.update) createSelector(selector, (state: State) => state.update)
),
NativeSelector: NativeStore.selectors(
createSelector(selector, (state: State) => state.native)
) )
}; };
} }

View File

@ -36,6 +36,7 @@ export class Effects {
), ),
{ dispatch: false } { dispatch: false }
); );
constructor( constructor(
private actions$: Actions, private actions$: Actions,
private translateService: TranslateService, private translateService: TranslateService,

View File

@ -0,0 +1,17 @@
import { createAction, props } from '@ngrx/store';
export const changeZoom = createAction(
'[Setting::Native] Change Zoom',
props<{ zoom: number }>()
);
export const changeZoomSuccess = createAction(
'[Setting::Native] changeZoom Success',
props<{
zoom: number;
}>()
);
export const changeZoomFailure = createAction(
'[Setting::Native] changeZoom Failure',
props<{ error: any }>()
);

View File

@ -0,0 +1,77 @@
import { Injectable, Inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { TranslateService as UCapTranslateService } from '@ucap-webmessenger/ui';
import { DateService as UCapDateService } from '@ucap-webmessenger/ui';
import * as AuthenticationStore from '@app/store/account/authentication';
import { LocalStorageService } from '@ucap-webmessenger/web-storage';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { environment } from '../../../../environments/environment';
import { changeZoom, changeZoomSuccess, changeZoomFailure } from './actions';
import { UCAP_NATIVE_SERVICE, NativeService } from '@ucap-webmessenger/native';
import { Store } from '@ngrx/store';
@Injectable()
export class Effects {
postLogin$ = createEffect(
() =>
this.actions$.pipe(
ofType(AuthenticationStore.postLogin),
map(action => action.loginRes),
tap(async loginRes => {
const appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
this.store.dispatch(changeZoom({ zoom: appUserInfo.zoom }));
})
),
{ dispatch: false }
);
changeZoom$ = createEffect(
() =>
this.actions$.pipe(
ofType(changeZoom),
map(action => action.zoom),
tap(zoom => {
const appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
this.nativeService
.zoomTo(zoom)
.then(f => {
this.localStorageService.encSet<AppUserInfo>(
KEY_APP_USER_INFO,
{
...appUserInfo,
zoom
},
environment.customConfig.appKey
);
this.store.dispatch(changeZoomSuccess({ zoom }));
})
.catch(reason => {
this.store.dispatch(changeZoomFailure({ error: reason }));
});
})
),
{ dispatch: false }
);
constructor(
private actions$: Actions,
private store: Store<any>,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private localStorageService: LocalStorageService
) {}
}

View File

@ -0,0 +1,4 @@
export * from './actions';
export * from './effects';
export * from './reducers';
export * from './state';

View File

@ -0,0 +1,13 @@
import { createReducer, on } from '@ngrx/store';
import { State, initialState } from './state';
import { changeZoomSuccess } from './actions';
export const reducer = createReducer(
initialState,
on(changeZoomSuccess, (state, action) => {
return {
...state,
zoom: action.zoom
} as State;
})
);

View File

@ -0,0 +1,16 @@
import { Selector, createSelector } from '@ngrx/store';
// tslint:disable-next-line: no-empty-interface
export interface State {
zoom: number;
}
export const initialState: State = {
zoom: 100
};
export function selectors<S>(selector: Selector<any, State>) {
return {
selectZoom: createSelector(selector, (state: State) => state.zoom)
};
}

View File

@ -11,6 +11,7 @@ export interface AppUserInfo {
companyGroupType?: string; companyGroupType?: string;
localeCode?: LocaleCode; localeCode?: LocaleCode;
idleCheckTime?: number; idleCheckTime?: number;
zoom?: number;
settings?: Settings; settings?: Settings;
} }

View File

@ -67,6 +67,10 @@
"failToChangeProfileImage": "Failed to change profile image." "failToChangeProfileImage": "Failed to change profile image."
} }
}, },
"information": {
"label": "Information",
"version": "Version"
},
"settings": { "settings": {
"label": "Settings", "label": "Settings",
"typeGenernal": "Genernal", "typeGenernal": "Genernal",

View File

@ -67,6 +67,10 @@
"failToChangeProfileImage": "프로필 이미지 변경에 실패 하였습니다." "failToChangeProfileImage": "프로필 이미지 변경에 실패 하였습니다."
} }
}, },
"information": {
"label": "정보",
"version": "버전"
},
"settings": { "settings": {
"label": "설정", "label": "설정",
"typeGenernal": "일반", "typeGenernal": "일반",

View File

@ -78,6 +78,12 @@ export class BrowserNativeService extends NativeService {
}); });
} }
getVersionInfo(): Promise<string> {
return new Promise<any>((resolve, reject) => {
resolve('');
});
}
changeAutoLaunch(autoLaunch: boolean): Promise<boolean> { changeAutoLaunch(autoLaunch: boolean): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
resolve(true); resolve(true);
@ -215,6 +221,12 @@ export class BrowserNativeService extends NativeService {
windowMaximize(): void {} windowMaximize(): void {}
zoomTo(factor: number): Promise<number> {
return new Promise<number>((resolve, reject) => {
resolve(-1);
});
}
idleStateChanged(): Observable<WindowIdle> { idleStateChanged(): Observable<WindowIdle> {
return new Observable<WindowIdle>(subscriber => { return new Observable<WindowIdle>(subscriber => {
try { try {

View File

@ -1,4 +1,4 @@
import { ipcRenderer, remote, shell } from 'electron'; import { ipcRenderer, remote, shell, webFrame } from 'electron';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
@ -34,6 +34,7 @@ import { StatusCode } from '@ucap-webmessenger/core';
}) })
export class ElectronNativeService implements NativeService { export class ElectronNativeService implements NativeService {
private ipcRenderer: typeof ipcRenderer; private ipcRenderer: typeof ipcRenderer;
private webFrame: typeof webFrame;
private remote: typeof remote; private remote: typeof remote;
private shell: typeof shell; private shell: typeof shell;
@ -89,6 +90,16 @@ export class ElectronNativeService implements NativeService {
}); });
} }
getVersionInfo(): Promise<string> {
return new Promise<string>((resolve, reject) => {
try {
resolve(this.ipcRenderer.sendSync(MessengerChannel.GetVersionInfo));
} catch (error) {
reject(error);
}
});
}
changeStatus(): Observable<StatusCode> { changeStatus(): Observable<StatusCode> {
if (!this.changeStatusSubject) { if (!this.changeStatusSubject) {
this.changeStatusSubject = new Subject<StatusCode>(); this.changeStatusSubject = new Subject<StatusCode>();
@ -377,6 +388,17 @@ export class ElectronNativeService implements NativeService {
} }
} }
zoomTo(factor: number): Promise<number> {
return new Promise<number>((resolve, reject) => {
try {
this.webFrame.setZoomFactor(factor / 100);
resolve(this.webFrame.getZoomFactor());
} catch (error) {
reject(error);
}
});
}
idleStateChanged(): Observable<WindowIdle> { idleStateChanged(): Observable<WindowIdle> {
if (!this.idleStateChangedSubject) { if (!this.idleStateChangedSubject) {
this.idleStateChangedSubject = new Subject<WindowIdle>(); this.idleStateChangedSubject = new Subject<WindowIdle>();
@ -448,6 +470,7 @@ export class ElectronNativeService implements NativeService {
this.ipcRenderer = (window as any).require('electron').ipcRenderer; this.ipcRenderer = (window as any).require('electron').ipcRenderer;
this.remote = (window as any).require('electron').remote; this.remote = (window as any).require('electron').remote;
this.shell = (window as any).require('electron').shell; this.shell = (window as any).require('electron').shell;
this.webFrame = (window as any).require('electron').webFrame;
} }
} }
} }

View File

@ -5,7 +5,8 @@ export enum MessengerChannel {
ChangeAutoLaunch = 'UCAP::messenger::changeAutoLaunch', ChangeAutoLaunch = 'UCAP::messenger::changeAutoLaunch',
ChangeStartupHideWindow = 'UCAP::messenger::changeStartupHideWindow', ChangeStartupHideWindow = 'UCAP::messenger::changeStartupHideWindow',
ChangeDownloadPath = 'UCAP::messenger::changeDownloadPath', ChangeDownloadPath = 'UCAP::messenger::changeDownloadPath',
GetNetworkInfo = 'UCAP::messenger::getNetworkInfo' GetNetworkInfo = 'UCAP::messenger::getNetworkInfo',
GetVersionInfo = 'UCAP::messenger::getVersionInfo'
} }
export enum ChatChannel { export enum ChatChannel {

View File

@ -25,6 +25,7 @@ export abstract class NativeService {
abstract showSetting(): Observable<void>; abstract showSetting(): Observable<void>;
abstract getNetworkInfo(): Promise<any>; abstract getNetworkInfo(): Promise<any>;
abstract getVersionInfo(): Promise<string>;
abstract changeAutoLaunch(autoLaunch: boolean): Promise<boolean>; abstract changeAutoLaunch(autoLaunch: boolean): Promise<boolean>;
abstract changeStartupHideWindow( abstract changeStartupHideWindow(
@ -66,6 +67,7 @@ export abstract class NativeService {
abstract windowClose(): void; abstract windowClose(): void;
abstract windowMinimize(): void; abstract windowMinimize(): void;
abstract windowMaximize(): void; abstract windowMaximize(): void;
abstract zoomTo(factor: number): Promise<number>;
abstract idleStateChanged(): Observable<WindowIdle>; abstract idleStateChanged(): Observable<WindowIdle>;
abstract changeLimitOfIdleState(limitTime: number): void; abstract changeLimitOfIdleState(limitTime: number): void;

View File

@ -4,7 +4,13 @@ export enum UserInfoUpdateType {
// R: 인트로소개 // R: 인트로소개
Intro = 'R', Intro = 'R',
// T: 전화보이기여부 // T: 전화보이기여부
TelephoneVisible = 'T' TelephoneVisible = 'T',
/** StatusMessage1 */
StatusMessage1 = 'S1',
/** StatusMessage2 */
StatusMessage2 = 'S2',
/** StatusMessage3 */
StatusMessage3 = 'S3'
} }
export enum TelephoneVisibleType { export enum TelephoneVisibleType {

View File

@ -1,5 +1,5 @@
<!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가--> <!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가-->
<div class="list-item" *ngIf="message" matRipple> <!--<div class="list-item" *ngIf="message" matRipple>
<dl class="item-default"> <dl class="item-default">
<dt> <dt>
<mat-icon <mat-icon
@ -34,4 +34,39 @@
</div> </div>
</dd> </dd>
</dl> </dl>
</div>-->
<div class="list-item message-list" *ngIf="message" matRipple>
<dl class="item-default">
<dt>
<span class="name">
{{ message.userName }}
<span *ngIf="message.userCount > 1" class="text-accent-color number">
{{
'message.andOthers' | translate: { count: message.userCount - 1 }
}}
</span>
</span>
</dt>
<dd>
<mat-icon
*ngIf="!!message.resType && message.resType === ContentType.Image"
>image</mat-icon
>
<mat-icon
*ngIf="!!message.resType && message.resType === ContentType.AttachFile"
>attach_file</mat-icon
>
{{ message.title }}
</dd>
</dl>
<div class="date">
<span>{{ message.regDate | ucapDate: 'MM:DD' }}</span>
<span
*ngIf="message.type === MessageType.Receive && !message.readYn"
class="badge-new bg-warn-darkest"
>
N
</span>
</div>
</div> </div>

View File

@ -0,0 +1,52 @@
.message-list {
&.list-item {
dl {
display: flex;
flex-flow: column;
flex: 1 1 auto;
width: calc(100% - 80px);
dt {
.name {
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis;
display: block;
white-space: nowrap;
word-wrap: normal;
font-weight: 600;
span {
font-size: 13px;
}
}
}
dd {
color: #777777;
margin-top: 4px;
white-space: nowrap;
word-wrap: normal;
text-overflow: ellipsis;
display: block;
width: 100%;
position: relative;
overflow: hidden;
}
}
.date {
display: flex;
flex-flow: column;
align-self: flex-start;
.badge-new {
border-radius: 50%;
color: #ffffff;
padding: 0;
text-align: center;
align-self: flex-end;
width: 20px;
height: 20px;
font-size: 12px;
margin-top: 4px;
}
}
}
}

View File

@ -1,5 +1,5 @@
<!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가--> <!--체크박스 보여줄때는 <div class="list-item checkbox" matRipple> 클래스에 checkbox만 추가-->
<div class="list-item" *ngIf="userInfo" matRipple> <div class="list-item checkbox" *ngIf="userInfo" matRipple>
<!--pcOn , pcOut pcOff , pcOther--> <!--pcOn , pcOut pcOff , pcOther-->
<span class="presence" [ngClass]="getPresence(PresenceType.PC)"> </span> <span class="presence" [ngClass]="getPresence(PresenceType.PC)"> </span>
<dl class="item-default"> <dl class="item-default">

View File

@ -1,6 +1,6 @@
<div> <div>
<mat-list> <mat-list>
<h1 mat-subheader>{{ 'settings.chat.label' | translate }}</h1> <!-- <h1 mat-subheader>{{ 'settings.chat.label' | translate }}</h1>
<mat-list-item> <mat-list-item>
<span class="item-title">{{ <span class="item-title">{{
'settings.chat.fontFamily' | translate 'settings.chat.fontFamily' | translate
@ -37,7 +37,7 @@
</span> </span>
</mat-list-item> </mat-list-item>
<mat-divider></mat-divider> <mat-divider></mat-divider> -->
<h1 mat-subheader *ngIf="_isNodeWebkit"> <h1 mat-subheader *ngIf="_isNodeWebkit">
{{ 'settings.chat.file' | translate }} {{ 'settings.chat.file' | translate }}

View File

@ -0,0 +1,25 @@
<ng-container>
<ng-container #view *ngIf="!editMode">
<ng-content select="[ucapInlineEditInput='view']"></ng-content>
<span class="view-actions">
<button mat-icon-button aria-label="Edit" (click)="onClickEdit($event)">
<span class="mdi mdi-square-edit-outline mdi-20px"></span>
</button>
</span>
</ng-container>
<ng-container #edit *ngIf="editMode">
<ng-content select="[ucapInlineEditInput='edit']"></ng-content>
<span class="edit-actions">
<button mat-icon-button aria-label="Apply" (click)="onClickApply($event)">
<span class="mdi mdi-check mdi-20px"></span>
</button>
<button
mat-icon-button
aria-label="Cacel"
(click)="onClickCancel($event)"
>
<span class="mdi mdi-close mdi-20px"></span>
</button>
</span>
</ng-container>
</ng-container>

View File

@ -0,0 +1,7 @@
.view-actions {
margin-right: 0px;
}
.edit-actions {
margin-right: 0px;
}

View File

@ -0,0 +1,25 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InlineEditInputComponent } from './inline-edit-input.component';
describe('InlineEditInputComponent', () => {
let component: InlineEditInputComponent;
let fixture: ComponentFixture<InlineEditInputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [InlineEditInputComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InlineEditInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,43 @@
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'ucap-inline-edit-input',
templateUrl: './inline-edit-input.component.html',
styleUrls: ['./inline-edit-input.component.scss']
})
export class InlineEditInputComponent implements OnInit {
@Output()
edit = new EventEmitter<Event>();
@Output()
cancel = new EventEmitter<Event>();
@Output()
apply = new EventEmitter<Event>();
get editMode() {
return this._editMode;
}
set editMode(editMode: boolean) {
this._editMode = editMode;
}
// tslint:disable-next-line: variable-name
_editMode = false;
constructor() {}
ngOnInit() {}
onClickEdit(event: Event) {
this.editMode = true;
this.edit.emit(event);
}
onClickCancel(event: Event) {
this.editMode = false;
this.cancel.emit(event);
}
onClickApply(event: Event) {
this.editMode = false;
this.apply.emit(event);
}
}

View File

@ -38,6 +38,7 @@ import { PickDateComponent } from './components/pick-date.component';
import { PickTimeComponent } from './components/pick-time.component'; import { PickTimeComponent } from './components/pick-time.component';
import { StepInputComponent } from './components/step-input.component'; import { StepInputComponent } from './components/step-input.component';
import { StickerSelectorComponent } from './components/sticker-selector.component'; import { StickerSelectorComponent } from './components/sticker-selector.component';
import { InlineEditInputComponent } from './components/inline-edit-input.component';
import { BinaryViewerComponent } from './components/file-viewer/binary-viewer.component'; import { BinaryViewerComponent } from './components/file-viewer/binary-viewer.component';
import { DocumentViewerComponent } from './components/file-viewer/document-viewer.component'; import { DocumentViewerComponent } from './components/file-viewer/document-viewer.component';
@ -88,6 +89,7 @@ const COMPONENTS = [
PickTimeComponent, PickTimeComponent,
StepInputComponent, StepInputComponent,
TranslationSectionComponent, TranslationSectionComponent,
InlineEditInputComponent,
BinaryViewerComponent, BinaryViewerComponent,
DocumentViewerComponent, DocumentViewerComponent,

View File

@ -18,6 +18,7 @@ export * from './lib/components/pick-time.component';
export * from './lib/components/step-input.component'; export * from './lib/components/step-input.component';
export * from './lib/components/split-button.component'; export * from './lib/components/split-button.component';
export * from './lib/components/sticker-selector.component'; export * from './lib/components/sticker-selector.component';
export * from './lib/components/inline-edit-input.component';
export * from './lib/data-source/virtual-scroll-tree-flat.data-source'; export * from './lib/data-source/virtual-scroll-tree-flat.data-source';