zooming is implemented

This commit is contained in:
Richard Park 2020-01-13 13:55:54 +09:00
parent ff87a0540e
commit d47dd78e4b
25 changed files with 312 additions and 29 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';

View File

@ -274,14 +274,14 @@
{{ 'profile.open' | translate }} {{ 'profile.open' | translate }}
</button> </button>
</div> </div>
<!-- <div class="setting"> <div class="setting">
<button <button
mat-menu-item mat-menu-item
class="zoom minus-square" class="zoom minus-square"
(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"
@ -289,7 +289,7 @@
> >
확대 확대
</button> </button>
</div> --> </div>
<div class="setting"> <div class="setting">
<button mat-menu-item (click)="onClickNotice()"> <button mat-menu-item (click)="onClickNotice()">
{{ 'notice.label' | translate }} {{ 'notice.label' | translate }}
@ -334,13 +334,20 @@
<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
#statusMessage1
(click)="$event.stopPropagation()"
>
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage1 }}</span>
<span ucapInlineEditInput="edit"><input matInput type="text"/></span>
</ucap-inline-edit-input>
<!-- {{ loginRes?.statusMessage1 }} -->
</button> </button>
<button <!-- <button
mat-menu-item mat-menu-item
class="edit" class="edit"
(click)="onClickChangeStatusBusy($event, 1)" (click)="$event.stopPropagation(); statusMessage1.editMode = true"
></button> ></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)">

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';
@ -60,6 +61,8 @@ 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';
const zoomFactors = [60, 70, 85, 100, 120, 145, 170, 200];
@Component({ @Component({
selector: 'app-layout-native-top-bar', selector: 'app-layout-native-top-bar',
templateUrl: './top-bar.component.html', templateUrl: './top-bar.component.html',
@ -81,6 +84,9 @@ 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;
@ -129,24 +135,35 @@ 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)
); );
const appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
this.zoom = appUserInfo.zoom;
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -361,15 +378,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) {

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

@ -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

@ -215,6 +215,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;
@ -377,6 +378,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 +460,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

@ -66,6 +66,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

@ -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,33 @@
import { Component, OnInit } 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 {
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;
}
onClickCancel(event: Event) {
this.editMode = false;
}
onClickApply(event: Event) {
this.editMode = false;
}
}

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';